001: /* ****************************************************************************
002: * SOAPDataEncoder.java
003: *
004: * Compile XML directly to SWF bytecodes.
005: * ****************************************************************************/
006:
007: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
008: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
009: * Use is subject to license terms. *
010: * J_LZ_COPYRIGHT_END *********************************************************/
011:
012: package org.openlaszlo.remote.swf.soap.encoding;
013:
014: import java.io.*;
015: import java.util.*;
016: import java.util.Iterator;
017:
018: import javax.xml.namespace.QName;
019: import javax.xml.soap.Name;
020: import javax.xml.soap.SOAPElement;
021:
022: import org.openlaszlo.server.LPS;
023: import org.openlaszlo.iv.flash.api.*;
024: import org.openlaszlo.iv.flash.api.action.*;
025: import org.openlaszlo.iv.flash.util.*;
026: import org.openlaszlo.remote.swf.soap.LZSOAPUtils;
027: import org.openlaszlo.utils.ChainedException;
028: import org.openlaszlo.xml.internal.DataCommon;
029: import org.openlaszlo.xml.internal.DataContext;
030: import org.apache.axis.AxisFault;
031: import org.apache.log4j.Logger;
032: import org.xml.sax.*;
033: import org.xml.sax.helpers.AttributesImpl;
034:
035: import org.apache.axis.message.MessageElement;
036: import org.apache.axis.message.SOAPHeader;
037: import org.apache.axis.message.Text;
038:
039: /**
040: */
041: public class SOAPDataEncoder implements ContentHandler {
042:
043: /* Logger */
044: private static Logger mLogger = Logger
045: .getLogger(SOAPDataEncoder.class);
046:
047: /** Hint to allocate buffer size large enough to hold output. */
048: private int initsize = 0;
049: private static final int DEFAULT_BUFFER_SIZE = 4096;
050:
051: /**
052: * The SWF file
053: */
054: private FlashOutput mSWF = null;
055: /**
056: * Size of the SWF file.
057: */
058: private long mSize = -1;
059:
060: private int mSWFVersion;
061:
062: /**
063: * Constructs an empty SOAPDataEncoder.
064: */
065: public SOAPDataEncoder(int swfversion) {
066: mSWFVersion = swfversion;
067: }
068:
069: /**
070: * Workaround variable for bug 4680.
071: */
072: boolean isProgram = false;
073:
074: /**
075: * Constructs a SOAPDataEncoder with a buffer allocation size hint.
076: * @param initsize hint to allocate buffer size large enough to hold output.
077: */
078: public SOAPDataEncoder(int swfversion, int initsize) {
079: this (swfversion);
080: this .initsize = initsize;
081: }
082:
083: public SOAPDataEncoder(int swfversion, Vector v, SOAPHeader h) {
084: this (swfversion);
085: buildFromElements(v, h);
086: }
087:
088: public SOAPDataEncoder(int swfversion, Program p, SOAPHeader h) {
089: this (swfversion);
090: buildFromProgram(p, h);
091: }
092:
093: public SOAPDataEncoder buildFromFault(AxisFault fault) {
094: start();
095: String actor = fault.getFaultActor(); // can be null
096: QName code = fault.getFaultCode(); // can be null
097: String node = fault.getFaultNode(); // SOAP1.2
098: String reason = fault.getFaultReason(); // SOAP1.2==SOAP1.1 faultstring
099: String role = fault.getFaultRole(); // SOAP1.2==SOAP1.1 actor
100: String faultstring = fault.getFaultString(); // never null
101: QName[] subcodes = fault.getFaultSubCodes(); // can be null
102: // Element[] details = fault.getFaultDetails(); // can be null
103: // ArrayList headers = fault.getHeaders();
104:
105: int count = 0;
106: if (subcodes != null) {
107: program.push("subcodes");
108: for (int i = 0; i < subcodes.length; i++) {
109: LZSOAPUtils.pushQName(program, subcodes[i], dc);
110: }
111: program.push(subcodes.length);
112: body.writeByte(Actions.InitArray);
113: count++;
114: }
115: if (actor != null) {
116: program.push("actor");
117: program.push(actor);
118: count++;
119: }
120: if (node != null) {
121: program.push("node");
122: program.push(node);
123: count++;
124: }
125: if (reason != null) {
126: program.push("reason");
127: program.push(reason);
128: count++;
129: }
130: if (role != null) {
131: program.push("role");
132: program.push(role);
133: count++;
134: }
135: if (faultstring != null) {
136: program.push("faultstring");
137: program.push(faultstring);
138: count++;
139: }
140: if (code != null) {
141: program.push("code");
142: LZSOAPUtils.pushQName(program, code, dc);
143: count++;
144: }
145:
146: program.push("errortype");
147: program.push("fault");
148:
149: program.push(count + 1);
150: body.writeByte(Actions.InitObject);
151: end();
152:
153: return this ;
154: }
155:
156: public SOAPDataEncoder buildFromException(Exception e) {
157: start();
158: {
159: Throwable cause = e.getCause();
160: String message = e.getMessage();
161: StringWriter sw = new StringWriter();
162: e.printStackTrace(new PrintWriter(sw));
163:
164: int count = 3;
165: program.push("stacktrace");
166: program.push(sw.toString());
167:
168: program.push("faultString");
169: if (message != null) {
170: program.push(message);
171: } else {
172: program.push(sw.toString());
173: }
174:
175: if (cause != null) {
176: program.push("cause");
177: program.push(cause.toString());
178: count++;
179: }
180:
181: program.push("errortype");
182: program.push("exception");
183:
184: program.push(count);
185:
186: body.writeByte(Actions.InitObject);
187: }
188: end();
189: return this ;
190: }
191:
192: //============================================================
193: // SAX API
194: //============================================================
195:
196: /**
197: * Receive notification of character data.
198: *
199: * @param ch the characters from the XML document.
200: * @param start the start position in the array.
201: * @param length the number of characters to read from the array.
202: *
203: * @see #characters(String) characters(String)
204: */
205: public void characters(char[] ch, int start, int length) {
206: String text = new String(ch, start, length);
207: characters(text);
208: }
209:
210: /**
211: * Receive notification of string data.
212: *
213: * @param text the string from the XML document.
214: *
215: * @see #characters(char[], int, int) characters(char[], int, int)
216: */
217: public void characters(String text) {
218: // makeTextNode = function (text, parent)
219: // dup pointer to parent (who is at top of stack)
220: // DUP
221: body.writeByte(Actions.PushDuplicate);
222: // Push text
223:
224: body.writeByte(Actions.PushData);
225: // Leave a two byte space for the PUSH length
226: // Mark where we are, so we can rewrite this with correct length later.
227: int push_bufferpos = body.getPos();
228: body.writeWord(0); // placeholder 16-bit length field
229:
230: DataCommon.pushMergedStringData(text, body, dc);
231: // Set up argsnum and function name
232: // PUSH 2, _tdn
233: body.writeByte(0x07); // INT type
234: body.writeDWord(2); // '2' integer constant ; number of args to function
235: body.writeByte(0x08); // SHORT DICTIONARY LOOKUP
236: body.writeByte(textnode_idx); // push function name: index of "_t" string constant
237:
238: // Now go back and fix up the size arg to the PUSH instruction
239: int total_size = body.getPos() - (push_bufferpos + 2);
240: //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
241: body.writeWordAt(total_size, push_bufferpos);
242:
243: body.writeByte(Actions.CallFunction);
244: // Pop the node, because there will be no end tag for it, and it has no children.
245: body.writeByte(Actions.Pop);
246: }
247:
248: /**
249: * Receive notification of the end of an element. This method is equivalent
250: * to calling {@link #endElement() endElement()} -- the input parameters are
251: * ignored.
252: *
253: * @param uri the Namespace URI, or the empty string if the element has no
254: * Namespace URI or if Namespace processing is not being performed.
255: * @param localName the local name (without prefix), or the empty string if
256: * Namespace processing is not being performed.
257: * @param qName the qualified XML 1.0 name (with prefix), or the empty
258: * string if qualified names are not available.
259: * @see #endElement() endElement()
260: */
261: public void endElement(String uri, String localName, String qName) {
262: // Pop the node off the stack.
263: body.writeByte(Actions.Pop);
264: }
265:
266: /**
267: * Receive notification of the end of an element.
268: *
269: * @see #endElement(String,String,String) endElement(String,String,String)
270: */
271: public void endElement() {
272: // Pop the node off the stack.
273: body.writeByte(Actions.Pop);
274: }
275:
276: /**
277: * End the scope of a prefix-URI mapping. This method is unimplemented.
278: *
279: * @param prefix the prefix that was being mapped.
280: */
281: public void endPrefixMapping(String prefix) {
282: }
283:
284: /**
285: * Receive notification of ignorable whitespace in element content. This
286: * method is unimplemented.
287: *
288: * @param ch the characters from the XML document.
289: * @param start the start position in the array.
290: * @param length the number of characters to read from the array.
291: */
292: public void ignorableWhitespace(char[] ch, int start, int length) {
293: }
294:
295: /**
296: * Receive notification of a processing instruction. This method is
297: * unimplemented.
298: *
299: * @param target the processing instruction target.
300: * @param data the processing instruction data, or null if none was
301: * supplied. The data does not include any whitespace separating it from the
302: * target.
303: */
304: public void processingInstruction(String target, String data) {
305: }
306:
307: /**
308: * Receive an object for locating the origin of SAX document events. This
309: * method is unimplemented.
310: *
311: * @param locator an object that can return the location of any SAX document
312: * event.
313: */
314: public void setDocumentLocator(Locator locator) {
315: }
316:
317: /**
318: * Receive notification of a skipped entity. This method is unimplemented.
319: *
320: * @param name the name of the skipped entity. If it is a parameter entity,
321: * the name will begin with '%', and if it is the external DTD subset, it
322: * will be the string "[dtd]".
323: */
324: public void skippedEntity(String name) {
325: }
326:
327: /** State vars */
328: DataContext dc;
329: // The node constructor function name.
330: private byte constructor_idx;
331: private byte textnode_idx;
332:
333: FlashBuffer body;
334: Program program;
335: Program resultProgram;
336: FlashBuffer out;
337:
338: public void start() {
339: // Room at the end of the buffer for maybe some callback code to the runtime to say we're done.
340: // Allocate enough room to hold the data nodes and strings ; it should be < input XML filesize
341: body = new FlashBuffer(initsize == 0 ? DEFAULT_BUFFER_SIZE
342: : initsize);
343: program = new Program(body);
344: dc = new DataContext();
345: }
346:
347: public void end() {
348: FlashBuffer body = program.body();
349:
350: // Call into viewsystem
351: program.push("_parent");
352: program.getVar();
353: program.push(3); // this, data, header
354: program.push("_parent");
355: program.getVar();
356: program.push("loader");
357: body.writeByte(Actions.GetMember);
358: program.push("returnData");
359: program.callMethod();
360: program.pop();
361:
362: // Collect the string dictionary data
363: byte pooldata[] = DataCommon.makeStringPool(dc);
364: // 'out' is the main FlashBuffer for composing the output file
365: final int MISC = 64;
366: out = new FlashBuffer(body.getSize() + pooldata.length + MISC);
367: // Write out string constant pool
368: out.writeByte(Actions.ConstantPool);
369: out.writeWord(pooldata.length + 2); // number of bytes in pool data + int (# strings)
370: out.writeWord(dc.cpool.size()); // number of strings in pool
371: out.writeArray(pooldata, 0, pooldata.length); // copy the data
372: // Write out the code to build nodes
373: out.writeArray(body.getBuf(), 0, body.getSize());
374: resultProgram = new Program(out);
375: }
376:
377: /**
378: * Receive notification of the beginning of a document.
379: *
380: * @see #endDocument() endDocument()
381: */
382: public void startDocument() {
383: start();
384: constructor_idx = (byte) (DataCommon.addStringConstant(
385: DataCommon.NODE_INSTANTIATOR_FN, dc) & 0xFF);
386: textnode_idx = (byte) (DataCommon.addStringConstant(
387: DataCommon.TEXT_INSTANTIATOR_FN, dc) & 0xFF);
388:
389: // Bind the node creation functions to some short local names:
390: // element nodes: _root._m => _m
391: program.push(new Object[] { "_m", "_root" });
392: program.getVar();
393: program.push("_m");
394: body.writeByte(Actions.GetMember);
395: program.setVar();
396:
397: // text nodes: _root._t => _t
398: program.push(new Object[] { "_t", "_root" });
399: program.getVar();
400: program.push("_t");
401: body.writeByte(Actions.GetMember);
402: program.setVar();
403: }
404:
405: /**
406: * Receive notification of the end of a document.
407: *
408: * @see #startDocument() startDocument()
409: */
410: public void endDocument() {
411: end();
412: }
413:
414: /**
415: * @return the Flash program to build the data set.
416: */
417: private Program getProgram() {
418: return resultProgram;
419: }
420:
421: /**
422: * Make SWF
423: *
424: * @return FlashFile containing entire SWF with header
425: */
426: private FlashFile makeSWFFile() {
427: // Create FlashFile object nd include action bytes
428: FlashFile file = FlashFile.newFlashFile();
429: Script s = new Script(1);
430: file.setMainScript(s);
431: file.setVersion(mSWFVersion);
432: Frame frame = s.newFrame();
433: frame.addFlashObject(new DoAction(resultProgram));
434: return file;
435: }
436:
437: /**
438: * Get the compiled data swf program byte codes. Only call this after you
439: * have called {@link #endDocument endDocument()}.
440: *
441: * @return input stream containing the compiled SWF data program; only valid
442: * after {@link #endDocument endDocument()} has been called. Must not be called
443: * before {@link #endDocument endDocument()}.
444: */
445: public InputStream getInputStream() throws IOException {
446:
447: generate();
448: return mSWF.getInputStream();
449: }
450:
451: /**
452: * Return the size of the output object; only valid after endDocument
453: * {@link #endDocument endDocument()} has been called. Must not be called
454: * before {@link #endDocument endDocument()}.
455: *
456: * @return long representing the size
457: */
458: public long getSize() throws IOException {
459:
460: generate();
461: return mSize;
462: }
463:
464: /**
465: * Generate the SWF file
466: */
467: private void generate() throws IOException {
468:
469: if (mSWF == null) {
470: try {
471: InputStream input;
472: FlashFile file = makeSWFFile();
473: mSWF = file.generate();
474: mSize = mSWF.pos;
475: } catch (IVException ex) {
476: throw new ChainedException(ex);
477: }
478: }
479: }
480:
481: /**
482: * A lower level call than startElement(); attributes must be supplied by
483: * individual calls to addAttribute(). This method is unimplemented.
484: * @param localName the element name.
485: */
486: public void _startElement(String localName) {
487:
488: }
489:
490: /**
491: * A low level call to add an attribute, must be preceded by call to
492: * _startElement() for a given element. This method is unimplemented.
493: */
494: public void addAttribute(String attrName, String attrVal) {
495:
496: }
497:
498: /**
499: * Receive notification of the beginning of an element. This method is
500: * equivalent to calling {@link #startElement(String, Attributes)
501: * startElement(String, Attributes)} -- the uri and qName parameters are
502: * ignored.
503: *
504: * @param uri the Namespace URI, or the empty string if the element has no
505: * Namespace URI or if Namespace processing is not being performed.
506: * @param localName the local name (without prefix), or the empty string if
507: * Namespace processing is not being performed.
508: * @param qName the qualified name (with prefix), or the empty string if
509: * qualified names are not available.
510: * @param atts the attributes attached to the element. If there are no
511: * attributes, it shall be an empty Attributes object.
512: *
513: * @see #startElement(String, Attributes) startElement(String, Attributes)
514: */
515: public void startElement(String uri, String localName,
516: String qName, Attributes atts) {
517: startElement(localName, atts);
518: }
519:
520: /**
521: * Receive notification of the beginning of an element.
522: *
523: * @param localName the local name (without prefix), or the empty string if
524: * Namespace processing is not being performed.
525: * @param atts the attributes attached to the element. If there are no
526: * attributes, it shall be an empty Attributes object.
527: *
528: * @see #startElement(String, String, String, Attributes)
529: * startElement(String, String, String, Attributes)
530: */
531: public void startElement(String localName, Attributes atts) {
532: int idx; // tmp var to hold a string pool index
533: // makeNodeNoText = function (attrs, name, parent)
534: // dup pointer to PARENT (who is at top of stack)
535: // DUP
536: body.writeByte(Actions.PushDuplicate);
537:
538: // We're really squeezing things down, so we are going to merge
539: // the PUSH of the element name with the PUSH of the attribute key/value
540: // data and the attribute count. So the stack will look like
541: // [eltname attrname1 attrval1 attrname2 attrval2 ... ... nattrs]
542: // when we're done
543: body.writeByte(Actions.PushData);
544: // Leave a two byte space for the PUSH length
545: // Mark where we are, so we can rewrite this with correct length later.
546: int push_bufferpos = body.getPos();
547: body.writeWord(0); // placeholder 16-bit length field
548:
549: // Push element NAME
550: String eltname = localName;
551: DataCommon.pushMergedStringDataSymbol(eltname, body, dc);
552:
553: // Fold all the attribute key/value pairs into a single PUSH
554: // Build ATTRIBUTE object
555: int nattrs = atts.getLength();
556:
557: // PUSH := {0x96, lenlo, lenhi, 0x00, char, char, char, ...0x00, }
558: for (int i = 0; i < nattrs; i++) {
559: String attrname = atts.getLocalName(i);
560: //System.out.print("Attr " + attrname);
561: DataCommon.pushMergedStringDataSymbol(attrname, body, dc);
562:
563: String attrval = atts.getValue(i);
564: //System.out.println("= " + attrval);
565: DataCommon.pushMergedStringData(attrval, body, dc);
566: }
567: // create the attrs object; push the attr count
568: body.writeByte(0x07); // INT type
569: body.writeDWord(nattrs);
570: // Now go back and fix up the size arg to the PUSH instruction
571: int total_size = body.getPos() - (push_bufferpos + 2);
572: //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
573: body.writeWordAt(total_size, push_bufferpos);
574: body.writeByte(Actions.InitObject);
575:
576: // stack now has [parent, name, attrs]
577: // Push # of args and node-instantiator-function name
578: // PUSH 3, _mdn
579: // [PUSHDATA, 7, 0, 0x07, 03 00 00 00 0x08, constructor_idx]
580: body.writeByte(Actions.PushData);
581: body.writeWord(7);
582: body.writeByte(0x07); // INT type
583: body.writeDWord(3); // '3' integer constant , number of args to node constructor fn
584: body.writeByte(0x08); // SHORT DICTIONARY LOOKUP type
585: body.writeByte(constructor_idx); // index of "_m" string constant
586: body.writeByte(Actions.CallFunction);
587: // We leave the new node on the stack, so we can reference it as the parent
588: // Stack => [parentnode newnode]
589: }
590:
591: /**
592: * Begin the scope of a prefix-URI Namespace mapping. This method is
593: * unimplemented.
594: *
595: * @param prefix the Namespace prefix being declared.
596: * @param uri the Namespace URI the prefix is mapped to.
597: */
598: public void startPrefixMapping(String prefix, String uri) {
599: }
600:
601: //============================================================
602: // End SAX ContentHandler Compatibility
603: //============================================================
604:
605: private void buildHeaders(SOAPHeader h) {
606: int hCount = 0;
607: Iterator iter = h.getChildElements();
608: while (iter.hasNext()) {
609: hCount++;
610: traverseDOM((SOAPElement) iter.next());
611: }
612: if (hCount != 0) {
613: program.push(hCount);
614: body.writeByte(Actions.InitArray);
615: } else {
616: LZSOAPUtils.pushNull(body);
617: }
618: }
619:
620: /**
621: * Build from a vector of SOAPElement items.
622: *
623: * @param v vector of SOAPElement items.
624: */
625: public void buildFromElements(Vector v, SOAPHeader h) {
626: startDocument();
627:
628: // soap headers
629: buildHeaders(h);
630:
631: // array of documents
632: for (int i = v.size() - 1; i >= 0; --i) {
633: traverseDOM((SOAPElement) v.get(i));
634: }
635: program.push(v.size());
636: body.writeByte(Actions.InitArray);
637:
638: endDocument();
639: }
640:
641: /**
642: * Build from a vector of SOAPElement items.
643: *
644: * @param v vector of SOAPElement items.
645: */
646: public void buildFromProgram(Program p, SOAPHeader h) {
647: // Workaround variable for bug 4680
648: isProgram = true;
649:
650: startDocument();
651:
652: // soap headers
653: buildHeaders(h);
654:
655: // program
656: if (p == null) {
657: LZSOAPUtils.pushNull(body);
658: } else {
659: body.writeFOB(p.body());
660: }
661:
662: endDocument();
663: }
664:
665: /**
666: * Traverse DOM for single SOAPElement.
667: */
668: private void traverseDOM(SOAPElement el) {
669: // Build a root node by calling the runtime's root node instantiator
670: //
671: // The root node will have $n = 0, and whatever other initial conditions are needed.
672: program.push(0); // Root node creator function takes no args.
673: program.push("_root");
674: program.getVar();
675: program.push(DataCommon.ROOT_NODE_INSTANTIATOR_FN);
676: program.callMethod();
677: // The root node is now on the stack.
678: // Build data. Invariant is that it leaves the stack the way it found it.
679:
680: AttributesImpl emptyAttr = new AttributesImpl();
681: // startElement("resultset", emptyAttr);
682: // startElement("body", emptyAttr);
683:
684: // we're now set up for a single document...let's traverse the element
685: _traverseDOM(el);
686:
687: // endElement();
688: // endElement();
689:
690: // The root node is sitting on the stack. Finalize; bind the variable
691: // "root" to the node that the lfc expects.
692: program.push(1);
693: program.push("_root");
694: program.getVar();
695: program.push(DataCommon.ROOT_NODE_FINAL_FN);
696: program.callMethod();
697: }
698:
699: /**
700: * Help traverse the DOM. Start point is traverseDOM().
701: */
702: private void _traverseDOM(SOAPElement el) {
703: AttributesImpl attrs = new AttributesImpl();
704: Iterator iter = el.getAllAttributes();
705: while (iter.hasNext()) {
706: Name attrName = (Name) iter.next();
707: String attrValue = el.getAttributeValue(attrName);
708: attrs.addAttribute(attrName.getURI(), attrName
709: .getLocalName(), attrName.getQualifiedName(), "",
710: attrValue);
711: }
712:
713: Name elName = el.getElementName();
714: startElement(elName.getLocalName(), attrs);
715:
716: iter = el.getChildElements();
717: while (iter.hasNext()) {
718: Object o = iter.next();
719: if (Text.class.isInstance(o)) {
720: // add text node
721: characters(((Text) o).getValue());
722: } else {
723: SOAPElement child = (SOAPElement) o;
724: _traverseDOM(child);
725: }
726: }
727:
728: endElement();
729:
730: }
731:
732: }
|