001: /* ****************************************************************************
002: * XMLRPCCompiler.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2006 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.xml.internal;
011:
012: import java.io.*;
013: import java.util.*;
014: import org.w3c.dom.*;
015: import org.xml.sax.*;
016: import javax.xml.parsers.*;
017: import org.openlaszlo.iv.flash.util.*;
018: import org.openlaszlo.iv.flash.api.action.*;
019: import org.openlaszlo.iv.flash.api.*;
020: import org.openlaszlo.utils.ChainedException;
021: import org.openlaszlo.utils.FileUtils;
022: import org.apache.log4j.*;
023:
024: /**
025: * Use XMLRPCCompiler.compile().
026: */
027: public class XMLRPCCompiler {
028: public static Logger mLogger = Logger
029: .getLogger(XMLRPCCompiler.class);
030:
031: private static DocumentBuilderFactory factory = null;
032:
033: private FlashBuffer body;
034: private Program program;
035:
036: /**
037: * Get document builder factory.
038: */
039: static DocumentBuilderFactory getDocumentBuilderFactory() {
040: if (factory == null) {
041: try {
042: factory = DocumentBuilderFactory.newInstance();
043: } catch (FactoryConfigurationError e) {
044: throw new RuntimeException(e.getMessage());
045: }
046: }
047: return factory;
048: }
049:
050: /**
051: * Get the list of first-level children based on tag name.
052: * @param parent element to retrieve first-level children based on tag name.
053: * @param tag tag based to search children.
054: */
055: static List getElementsByTagName(Element parent, String tag) {
056: List list = new Vector();
057: NodeList children = parent.getChildNodes();
058:
059: for (int i = 0; i < children.getLength(); i++) {
060: Node node = children.item(i);
061: if (node.getNodeType() == Node.ELEMENT_NODE) {
062: list.add((Element) node);
063: }
064: }
065: return list;
066: }
067:
068: /**
069: * Skips through comments and spaces.
070: * @param parent an element.
071: * @param n the nth child element to get from parent (starting from 0).
072: */
073: static Element getChildElement(Element parent, int n) {
074: NodeList children = parent.getChildNodes();
075: int count = 0;
076: for (int i = 0; i < children.getLength(); i++) {
077: Node node = children.item(i);
078: if (node.getNodeType() == Node.ELEMENT_NODE) {
079: if (n == count++) {
080: mLogger.debug("found " + node.getNodeName());
081: return (Element) node;
082: }
083: }
084: }
085: return null;
086: }
087:
088: /**
089: * Get text string of an element.
090: * @param parent element to get text from.
091: */
092: static String getFirstChildTextString(Element parent) {
093: Node text = parent.getFirstChild();
094: return text != null && text.getNodeType() == Node.TEXT_NODE ? ((Text) text)
095: .getData()
096: : "";
097: }
098:
099: /**
100: * Write XMLRPC string response to SWF.
101: * @param string XMLRPC string to push.
102: */
103: void writeString(String string) throws IOException {
104: mLogger.debug("writeString");
105: program.push(string);
106: }
107:
108: /**
109: * Write XMLRPC integer response to SWF.
110: * @param intval XMLRPC integer string response to parse and push.
111: */
112: void writeInteger(String intval) throws IOException {
113: mLogger.debug("writeInteger");
114: try {
115: program.push(Integer.parseInt(intval));
116: } catch (NumberFormatException e) {
117: throw new IOException(e.getMessage());
118: }
119: }
120:
121: /**
122: * Write XMLRPC double response to SWF.
123: * @param doubleval XMLRPC 'double' string response to parse to float and
124: * push.
125: */
126: void writeDouble(String doubleval) throws IOException {
127: mLogger.debug("writeDouble");
128: try {
129: // Program doesn't have a push(double).
130: // This code taken from Program.push( Object data) in jgenerator-2.2.
131: body.writeByte(Actions.PushData);
132: body.writeWord(8 + 1);
133: body.writeByte(6);
134: long dbits = Double.doubleToLongBits(Double
135: .parseDouble(doubleval));
136: body.writeDWord((int) (dbits >>> 32));
137: body.writeDWord((int) (dbits & 0xffffffffL));
138: } catch (NumberFormatException e) {
139: throw new IOException(e.getMessage());
140: }
141: }
142:
143: /**
144: * Snarfed from Program.push(Object o) in jgenerator-2.2.
145: */
146: void pushBoolean(boolean b) {
147: body.writeByte(Actions.PushData);
148: body.writeWord(1 + 1);
149: body.writeByte(5);
150: body.writeByte(b ? 1 : 0);
151: }
152:
153: /**
154: * Write XMLRPC boolean response to SWF.
155: * @param booleanval XMLRPC boolean string response to parse and push.
156: */
157: void writeBoolean(String booleanval) throws IOException {
158: mLogger.debug("writeBoolean");
159: try {
160: pushBoolean(Integer.parseInt(booleanval) != 0);
161: } catch (NumberFormatException e) {
162: if (booleanval.equals("false"))
163: pushBoolean(false);
164: else if (booleanval.equals("true"))
165: pushBoolean(true);
166: else
167: throw new IOException("not a boolean value");
168: }
169: }
170:
171: /**
172: * Write XMLRPC array response to SWF.
173: * @param array XMLRPC array element to parse and push.
174: */
175: void writeArray(Element array) throws IOException {
176: mLogger.debug("writeArray");
177: List data = getElementsByTagName(array, "data");
178: if (data.size() != 1)
179: throw new IOException(
180: "Invalid number of data elements in array");
181:
182: List values = getElementsByTagName((Element) data.get(0),
183: "value");
184: for (int i = values.size() - 1; i >= 0; --i) {
185: writeValue((Element) values.get(i));
186: }
187: // Now push the array length and initialize it
188: program.push(values.size());
189: body.writeByte(Actions.InitArray);
190: }
191:
192: /**
193: * Write XMLRPC struct response to SWF.
194: * @param struct XMLRPC struct element to parse and push.
195: */
196: void writeStruct(Element struct) throws IOException {
197: mLogger.debug("writeStruct");
198: List members = getElementsByTagName(struct, "member");
199: // Push member values in reverse order
200: for (int i = members.size() - 1; i >= 0; --i) {
201: writeMember((Element) members.get(i));
202: }
203: // Now push length of hash values and initialize it
204: program.push(members.size());
205: body.writeByte(Actions.InitObject);
206: }
207:
208: /**
209: * Write XMLRPC struct member to SWF. Helper for writeStruct.
210: * @param member XMLRPC struct member element to parse and push.
211: */
212: void writeMember(Element member) throws IOException {
213: mLogger.debug("writeMember");
214: Element name = getChildElement(member, 0);
215: Element value = getChildElement(member, 1);
216: if (name == null || value == null)
217: throw new IOException("Name or value appears to be null.");
218: if (!name.getNodeName().equals("name"))
219: throw new IOException(
220: "Name does not appear to be the first argument in member");
221: if (!value.getNodeName().equals("value"))
222: throw new IOException(
223: "Value does not appear to be the second argument in member");
224:
225: // when initing hash, value are popped before names
226: program.push(getFirstChildTextString(name));
227: writeValue(value);
228: }
229:
230: /**
231: * Unimplemented.
232: */
233: void writeDateTime(Element datetimeval) throws RuntimeException {
234: mLogger.debug("writeDateTime");
235: throw new RuntimeException("datetime.iso8601 unimplemented");
236: }
237:
238: /**
239: * Unimplemented.
240: */
241: void writeBase64(Element base64val) {
242: mLogger.debug("writeBase64");
243: throw new RuntimeException("base64 unimplemented");
244: }
245:
246: /**
247: * Write XMLRPC response to SWF.
248: * @param value
249: */
250: void writeValue(Element value) throws IOException {
251: mLogger.debug("writeValue");
252:
253: Element type = getChildElement(value, 0);
254: if (type == null) {
255: writeString(getFirstChildTextString(value));
256: return;
257: }
258:
259: String t = type.getTagName();
260: if (t.equals("string")) {
261: writeString(getFirstChildTextString(type));
262: } else if (t.equals("int") || t.equals("i4")) {
263: writeInteger(getFirstChildTextString(type));
264: } else if (t.equals("double")) {
265: writeDouble(getFirstChildTextString(type));
266: } else if (t.equals("boolean")) {
267: writeBoolean(getFirstChildTextString(type));
268: } else if (t.equals("struct")) {
269: writeStruct(type);
270: } else if (t.equals("array")) {
271: writeArray(type);
272: } else if (t.equals("dateTime.iso8601")) {
273: writeDateTime(type);
274: } else if (t.equals("base64")) {
275: writeBase64(type);
276: }
277: }
278:
279: /**
280: *
281: */
282: void writeParams(Element params) throws IOException {
283: mLogger.debug("writeParams");
284:
285: Element param = getChildElement(params, 0);
286: if (!param.getTagName().equals("param"))
287: throw new IOException("Invalid params body");
288:
289: Element value = getChildElement(param, 0);
290: if (!value.getTagName().equals("value"))
291: throw new IOException("Invalid param body");
292:
293: writeValue(value);
294: }
295:
296: /**
297: * Write XMLRPC fault response struct to SWF.
298: * @param fault the fault element.
299: */
300: void writeFault(Element fault) throws IOException {
301: mLogger.debug("writeFault");
302:
303: Element value = getChildElement(fault, 0);
304: if (!value.getTagName().equals("value"))
305: throw new IOException("Invalid param body");
306:
307: writeValue(value);
308: }
309:
310: /**
311: *
312: */
313: void writeXMLRPCData(Element element) throws IOException {
314: mLogger.debug("writeXMLRPCData");
315:
316: if (!element.getTagName().equals("methodResponse"))
317: throw new IOException("Invalid XMLRPC response");
318:
319: Element child = getChildElement(element, 0);
320: if (child != null) {
321: if (child.getTagName().equals("params")) {
322: writeParams(child);
323: return;
324: } else if (child.getTagName().equals("fault")) {
325: writeFault(child);
326: return;
327: }
328: }
329: // TODO: [2004-02-20 pkang] this should return actionscript for xmlrpc
330: // client lib
331: throw new IOException("bad XMLRPC message body");
332: }
333:
334: /**
335: * Produces a JGenerator Flash Program containing executable SWF codes to
336: * build an XML datasource structure which represents this XML stream.
337: *
338: * @param element xml response document element
339: * @param xmlsize size of xml used to create initial flash buffer.
340: * @return Flash Program containing entire SWF with header
341: */
342: public Program makeProgram(Element element, int xmlsize)
343: throws IOException {
344: mLogger.debug("makeProgram");
345:
346: // Room at the end of the buffer for maybe some callback code to the
347: // runtime to say we're done.
348: final int MISC = 4096;
349: // Allocate enough room to hold the data nodes and strings ; it should
350: // be < (input XML filesize * scalar)
351: body = new FlashBuffer((int) (Math.floor(xmlsize * 3) + MISC));
352: program = new Program(body);
353:
354: writeXMLRPCData(element);
355:
356: // call into the viewsystem
357: program.push("_parent");
358: program.getVar();
359: program.push(2);
360: program.push("_parent");
361: program.getVar();
362: program.push("loader");
363: body.writeByte(Actions.GetMember);
364: program.push("returnData");
365: program.callMethod();
366: program.pop();
367:
368: return program;
369: }
370:
371: /**
372: * Make SWF
373: *
374: * @return FlashFile containing entire SWF with header
375: */
376: public FlashFile makeSWF(Element element, int xmlsize,
377: int swfversion) throws IOException {
378: mLogger.debug("makeSWF");
379:
380: // Create FlashFile object nd include action bytes
381: FlashFile file = FlashFile.newFlashFile();
382: Script s = new Script(1);
383: file.setMainScript(s);
384: file.setVersion(swfversion);
385: Frame frame = s.newFrame();
386: Program program = makeProgram(element, xmlsize);
387: frame.addFlashObject(new DoAction(program));
388: return file;
389: }
390:
391: /**
392: * Get XML to output stream SWF
393: *
394: * @return swf input stream
395: */
396: public byte[] getSWF(Element element, int xmlsize, int swfversion)
397: throws IOException {
398: mLogger.debug("getSWF");
399: int i = 0;
400: try {
401: FlashFile file = makeSWF(element, xmlsize, swfversion);
402: FlashOutput fob = file.generate();
403: byte[] buf = new byte[fob.getSize()];
404: System.arraycopy(fob.getBuf(), 0, buf, 0, fob.getSize());
405: return buf;
406: } catch (IVException e) {
407: throw new ChainedException(e);
408: } catch (IOException e) {
409: mLogger.error("io error getting SWF: " + e.getMessage());
410: throw e;
411: }
412: }
413:
414: /**
415: * @see compile(Reader, int)
416: */
417: public static byte[] compile(String xmlrpc, int swfversion)
418: throws IOException {
419: return compile(new StringReader(xmlrpc), xmlrpc.length(),
420: swfversion);
421: }
422:
423: /**
424: * Compile the XMLRPC response to SWF bytecode.
425: *
426: * @return SWF bytecode for flash client.
427: */
428: public static byte[] compile(Reader reader, int xmlsize,
429: int swfversion) throws IOException {
430: mLogger.debug("compile(reader,xmlsize,swfversion)");
431: try {
432: // TODO: [2004-02-20 pkang] do we worry about character encoding?
433: DocumentBuilder builder = getDocumentBuilderFactory()
434: .newDocumentBuilder();
435: Document document = builder.parse(new InputSource(reader));
436: return new XMLRPCCompiler().getSWF(document
437: .getDocumentElement(), xmlsize, swfversion);
438: } catch (Exception e) {
439: mLogger.error("Caught exception at compile: " + e);
440: StringWriter trace = new StringWriter();
441: e.printStackTrace(new PrintWriter(trace));
442: return compileFault(trace.toString(), swfversion);
443: }
444: }
445:
446: public static String xmlFaultResponse(int code, String message) {
447: return new StringBuffer("<?xml version=\"1.0\"?>").append(
448: "<methodResponse>").append("<fault>").append("<value>")
449: .append("<struct>").append("<member>").append(
450: "<name>faultCode</name>")
451: .append("<value><int>").append(code).append(
452: "</int></value>").append("</member>").append(
453: "<member>").append("<name>faultString</name>")
454: .append("<value><string>").append(message).append(
455: "</string></value>").append("</member>")
456: .append("</struct>").append("</value>").append(
457: "</fault>").append("</methodResponse>")
458: .toString();
459: }
460:
461: public static byte[] compileResponse(int code, String message,
462: int swfversion) throws IOException {
463: String fault = xmlFaultResponse(code, message);
464: try {
465: DocumentBuilder builder = getDocumentBuilderFactory()
466: .newDocumentBuilder();
467: Document document = builder.parse(new InputSource(
468: new StringReader(fault)));
469: return new XMLRPCCompiler().getSWF(document
470: .getDocumentElement(), fault.length(), swfversion);
471: } catch (Exception e) {
472: mLogger.error("Caught exception at compileFault: "
473: + message, e);
474: throw new IOException(e.getMessage());
475: }
476: }
477:
478: /**
479: * Used by compiler to send back exception messages.
480: */
481: public static byte[] compileFault(String message, int swfversion)
482: throws IOException {
483: return compileResponse(-1, message, swfversion);
484: }
485:
486: /**
487: * Main.
488: */
489: public static void main(String[] args) {
490: System.out.println("args: " + args.length);
491: if (args.length != 1) {
492: System.err.println("Usage: XMLRPCCompiler xmlrpcfile");
493: return;
494: }
495: try {
496: File file = new File(args[0]);
497: InputStream in = new ByteArrayInputStream(compile(
498: new FileReader(file), (int) file.length(), 6));
499: OutputStream out = new FileOutputStream("xmlrpc.swf");
500: FileUtils.send(in, out, 4096);
501: } catch (Exception e) {
502: e.printStackTrace();
503: }
504: }
505: }
|