001: /* ****************************************************************************
002: * XMLRPCCompiler.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 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 java.text.*;
015: import org.w3c.dom.*;
016: import org.xml.sax.*;
017: import javax.xml.parsers.*;
018: import org.openlaszlo.sc.ScriptCompiler;
019: import org.openlaszlo.iv.flash.util.*;
020: import org.openlaszlo.iv.flash.api.action.*;
021: import org.openlaszlo.iv.flash.api.*;
022: import org.openlaszlo.utils.ChainedException;
023: import org.openlaszlo.utils.FileUtils;
024: import org.apache.log4j.*;
025:
026: /**
027: * Use XMLRPCJSONCompiler.compile().
028: */
029: public class XMLRPCJSONCompiler {
030: public static Logger mLogger = Logger
031: .getLogger(XMLRPCJSONCompiler.class);
032:
033: private static DocumentBuilderFactory factory = null;
034:
035: private StringBuffer body;
036:
037: /**
038: * Get document builder factory.
039: */
040: static DocumentBuilderFactory getDocumentBuilderFactory() {
041: if (factory == null) {
042: try {
043: factory = DocumentBuilderFactory.newInstance();
044: } catch (FactoryConfigurationError e) {
045: throw new RuntimeException(e.getMessage());
046: }
047: }
048: return factory;
049: }
050:
051: /**
052: * Get the list of first-level children based on tag name.
053: * @param parent element to retrieve first-level children based on tag name.
054: * @param tag tag based to search children.
055: */
056: static List getElementsByTagName(Element parent, String tag) {
057: List list = new Vector();
058: NodeList children = parent.getChildNodes();
059:
060: for (int i = 0; i < children.getLength(); i++) {
061: Node node = children.item(i);
062: if (node.getNodeType() == Node.ELEMENT_NODE) {
063: list.add((Element) node);
064: }
065: }
066: return list;
067: }
068:
069: /**
070: * Skips through comments and spaces.
071: * @param parent an element.
072: * @param n the nth child element to get from parent (starting from 0).
073: */
074: static Element getChildElement(Element parent, int n) {
075: NodeList children = parent.getChildNodes();
076: int count = 0;
077: for (int i = 0; i < children.getLength(); i++) {
078: Node node = children.item(i);
079: if (node.getNodeType() == Node.ELEMENT_NODE) {
080: if (n == count++) {
081: mLogger.debug("found " + node.getNodeName());
082: return (Element) node;
083: }
084: }
085: }
086: return null;
087: }
088:
089: /**
090: * Get text string of an element.
091: * @param parent element to get text from.
092: */
093: static String getFirstChildTextString(Element parent) {
094: Node text = parent.getFirstChild();
095: return text != null && text.getNodeType() == Node.TEXT_NODE ? ((Text) text)
096: .getData()
097: : "";
098: }
099:
100: /**
101: * Write XMLRPC string response to SWF.
102: * @param string XMLRPC string to push.
103: */
104: void writeString(String string) throws IOException {
105: body.append(ScriptCompiler.quote(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: body.append(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: body.append(doubleval);
130: } catch (NumberFormatException e) {
131: throw new IOException(e.getMessage());
132: }
133: }
134:
135: /**
136: * Snarfed from Program.push(Object o) in jgenerator-2.2.
137: */
138: void pushBoolean(boolean b) {
139: body.append(b ? "true" : "false");
140: }
141:
142: /**
143: * Write XMLRPC boolean response to SWF.
144: * @param booleanval XMLRPC boolean string response to parse and push.
145: */
146: void writeBoolean(String booleanval) throws IOException {
147: mLogger.debug("writeBoolean");
148: try {
149: pushBoolean(Integer.parseInt(booleanval) != 0);
150: } catch (NumberFormatException e) {
151: if (booleanval.equals("false"))
152: pushBoolean(false);
153: else if (booleanval.equals("true"))
154: pushBoolean(true);
155: else
156: throw new IOException("not a boolean value");
157: }
158: }
159:
160: /**
161: * Write XMLRPC array response to SWF.
162: * @param array XMLRPC array element to parse and push.
163: */
164: void writeArray(Element array) throws IOException {
165: mLogger.debug("writeArray");
166: body.append("[");
167: List data = getElementsByTagName(array, "data");
168: if (data.size() != 1)
169: throw new IOException(
170: "Invalid number of data elements in array");
171:
172: List values = getElementsByTagName((Element) data.get(0),
173: "value");
174: for (int i = values.size() - 1; i >= 0; --i) {
175: writeValue((Element) values.get(i));
176: if (i > 0) {
177: body.append(", ");
178: }
179: }
180: body.append("]");
181: }
182:
183: /**
184: * Write XMLRPC struct response to SWF.
185: * @param struct XMLRPC struct element to parse and push.
186: */
187: void writeStruct(Element struct) throws IOException {
188: mLogger.debug("writeStruct");
189: body.append("{");
190: List members = getElementsByTagName(struct, "member");
191: // Push member values in reverse order
192: for (int i = members.size() - 1; i >= 0; --i) {
193: writeMember((Element) members.get(i));
194: if (i > 0) {
195: body.append(", ");
196: }
197: }
198: body.append("}");
199: }
200:
201: /**
202: * Write XMLRPC struct member to SWF. Helper for writeStruct.
203: * @param member XMLRPC struct member element to parse and push.
204: */
205: void writeMember(Element member) throws IOException {
206: mLogger.debug("writeMember");
207:
208: Element name = getChildElement(member, 0);
209: Element value = getChildElement(member, 1);
210: if (name == null || value == null)
211: throw new IOException("Name or value appears to be null.");
212: if (!name.getNodeName().equals("name"))
213: throw new IOException(
214: "Name does not appear to be the first argument in member");
215: if (!value.getNodeName().equals("value"))
216: throw new IOException(
217: "Value does not appear to be the second argument in member");
218:
219: // when initing hash, value are popped before names
220: body.append("'" + getFirstChildTextString(name) + "'");
221: body.append(": ");
222: writeValue(value);
223: }
224:
225: // formats for parsing and generating dateTime values
226: static final DateFormat datetime = new SimpleDateFormat(
227: "yyyyMMdd HH:mm:ss");
228: static final DateFormat jsondatetime = new SimpleDateFormat(
229: "yyyy MM dd HH:mm:ss");
230: static final DateFormat date = new SimpleDateFormat("yyyyMMdd");
231: static final DateFormat time = new SimpleDateFormat("HH:mm:ss");
232:
233: /**
234: * Unimplemented.
235: */
236: void writeDateTime(Element datetimeval) throws RuntimeException {
237: mLogger.debug("writeDateTime");
238: try {
239: String dval = getFirstChildTextString(datetimeval);
240: String d = dval.trim().replace('T', ' ');
241: Date value = datetime.parse(d);
242: body.append(jsondatetime.format(value));
243: } catch (ParseException p) {
244: // System.out.println ("Exception while parsing date: "+p);
245: throw new RuntimeException(p.getMessage());
246: }
247: }
248:
249: /**
250: * Unimplemented.
251: */
252: void writeBase64(Element base64val) {
253: mLogger.debug("writeBase64");
254: throw new RuntimeException("base64 unimplemented");
255: }
256:
257: /**
258: * Write XMLRPC response to SWF.
259: * @param value
260: */
261: void writeValue(Element value) throws IOException {
262: mLogger.debug("writeValue");
263:
264: Element type = getChildElement(value, 0);
265: if (type == null) {
266: writeString(getFirstChildTextString(value));
267: return;
268: }
269:
270: String t = type.getTagName();
271: if (t.equals("string")) {
272: writeString(getFirstChildTextString(type));
273: } else if (t.equals("int") || t.equals("i4")) {
274: writeInteger(getFirstChildTextString(type));
275: } else if (t.equals("double")) {
276: writeDouble(getFirstChildTextString(type));
277: } else if (t.equals("boolean")) {
278: writeBoolean(getFirstChildTextString(type));
279: } else if (t.equals("struct")) {
280: writeStruct(type);
281: } else if (t.equals("array")) {
282: writeArray(type);
283: } else if (t.equals("dateTime.iso8601")) {
284: writeDateTime(type);
285: } else if (t.equals("base64")) {
286: writeBase64(type);
287: }
288: }
289:
290: /**
291: *
292: */
293: void writeParams(Element params) throws IOException {
294: mLogger.debug("writeParams");
295:
296: Element param = getChildElement(params, 0);
297: if (!param.getTagName().equals("param"))
298: throw new IOException("Invalid params body");
299:
300: Element value = getChildElement(param, 0);
301: if (!value.getTagName().equals("value"))
302: throw new IOException("Invalid param body");
303:
304: writeValue(value);
305: }
306:
307: /**
308: * Write XMLRPC fault response struct to SWF.
309: * @param fault the fault element.
310: */
311: void writeFault(Element fault) throws IOException {
312: mLogger.debug("writeFault");
313:
314: Element value = getChildElement(fault, 0);
315: if (!value.getTagName().equals("value"))
316: throw new IOException("Invalid param body");
317:
318: writeValue(value);
319: }
320:
321: /**
322: *
323: */
324: void writeXMLRPCData(Element element) throws IOException {
325: mLogger.debug("writeXMLRPCData");
326:
327: if (!element.getTagName().equals("methodResponse"))
328: throw new IOException("Invalid XMLRPC response");
329:
330: Element child = getChildElement(element, 0);
331: if (child != null) {
332: if (child.getTagName().equals("params")) {
333: writeParams(child);
334: return;
335: } else if (child.getTagName().equals("fault")) {
336: writeFault(child);
337: return;
338: }
339: }
340: // TODO: [2004-02-20 pkang] this should return actionscript for xmlrpc
341: // client lib
342: throw new IOException("bad XMLRPC message body");
343: }
344:
345: /**
346: * Send XML to output stream as JSON
347: *
348: * @return json input stream
349: */
350: public byte[] getSWF(Element element, String runtime)
351: throws IOException {
352: mLogger.debug("getSWF");
353: int i = 0;
354: try {
355: body = new StringBuffer();
356: writeXMLRPCData(element);
357: StringBuffer expr = new StringBuffer();
358: // hacked in callback to a global instance for now
359: expr.append(body.toString());
360: return expr.toString().getBytes("UTF-8");
361: } catch (IOException e) {
362: mLogger.error("io error getting SWF: " + e.getMessage());
363: throw e;
364: }
365: }
366:
367: /**
368: * @see compile(Reader, int)
369: */
370: public static byte[] compile(String xmlrpc, String runtime)
371: throws IOException {
372: return compile(new StringReader(xmlrpc), runtime);
373: }
374:
375: /**
376: * Compile the XMLRPC response to JSON expression.
377: *
378: * @return JSON expression for data.
379: */
380: public static byte[] compile(Reader reader, String runtime)
381: throws IOException {
382: mLogger.debug("compile(reader,runtime)");
383: try {
384: // TODO: [2004-02-20 pkang] do we worry about character encoding?
385: DocumentBuilder builder = getDocumentBuilderFactory()
386: .newDocumentBuilder();
387: Document document = builder.parse(new InputSource(reader));
388: return new XMLRPCJSONCompiler().getSWF(document
389: .getDocumentElement(), runtime);
390: } catch (Exception e) {
391: mLogger.error("Caught exception at compile: " + e);
392: StringWriter trace = new StringWriter();
393: e.printStackTrace(new PrintWriter(trace));
394: return compileFault(trace.toString(), runtime);
395: }
396: }
397:
398: public static String xmlFaultResponse(int code, String message) {
399: return new StringBuffer("<?xml version=\"1.0\"?>").append(
400: "<methodResponse>").append("<fault>").append("<value>")
401: .append("<struct>").append("<member>").append(
402: "<name>faultCode</name>")
403: .append("<value><int>").append(code).append(
404: "</int></value>").append("</member>").append(
405: "<member>").append("<name>faultString</name>")
406: .append("<value><string>").append(message).append(
407: "</string></value>").append("</member>")
408: .append("</struct>").append("</value>").append(
409: "</fault>").append("</methodResponse>")
410: .toString();
411: }
412:
413: public static byte[] compileResponse(int code, String message,
414: String runtime) throws IOException {
415: String fault = xmlFaultResponse(code, message);
416: try {
417: DocumentBuilder builder = getDocumentBuilderFactory()
418: .newDocumentBuilder();
419: Document document = builder.parse(new InputSource(
420: new StringReader(fault)));
421: return new XMLRPCJSONCompiler().getSWF(document
422: .getDocumentElement(), runtime);
423: } catch (Exception e) {
424: mLogger.error("Caught exception at compileFault: "
425: + message, e);
426: throw new IOException(e.getMessage());
427: }
428: }
429:
430: /**
431: * Used by compiler to send back exception messages.
432: */
433: public static byte[] compileFault(String message, String runtime)
434: throws IOException {
435: return compileResponse(-1, message, runtime);
436: }
437:
438: /**
439: * Main.
440: */
441: public static void main(String[] args) {
442: System.out.println("args: " + args.length);
443: if (args.length != 1) {
444: System.err.println("Usage: XMLRPCJSONCompiler xmlrpcfile");
445: return;
446: }
447: try {
448: File file = new File(args[0]);
449: InputStream in = new ByteArrayInputStream(compile(
450: new FileReader(file), "dhtml"));
451: OutputStream out = new FileOutputStream("xmlrpc.swf");
452: FileUtils.send(in, out, 4096);
453: } catch (Exception e) {
454: e.printStackTrace();
455: }
456: }
457: }
|