KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > remote > soap > encoding > SOAPDataEncoder


1 /* ****************************************************************************
2  * SOAPDataEncoder.java
3  *
4  * Compile XML directly to SWF bytecodes.
5  * ****************************************************************************/

6
7 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
8 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
9 * Use is subject to license terms. *
10 * J_LZ_COPYRIGHT_END *********************************************************/

11
12 package org.openlaszlo.remote.soap.encoding;
13
14 import java.io.*;
15 import java.util.*;
16 import java.util.Iterator JavaDoc;
17
18 import javax.xml.namespace.QName JavaDoc;
19 import javax.xml.soap.Name JavaDoc;
20 import javax.xml.soap.SOAPElement JavaDoc;
21
22 import org.openlaszlo.server.LPS;
23 import org.openlaszlo.iv.flash.api.*;
24 import org.openlaszlo.iv.flash.api.action.*;
25 import org.openlaszlo.iv.flash.util.*;
26 import org.openlaszlo.remote.soap.LZSOAPUtils;
27 import org.openlaszlo.utils.ChainedException;
28 import org.openlaszlo.xml.internal.DataCommon;
29 import org.openlaszlo.xml.internal.DataContext;
30 import org.apache.axis.AxisFault;
31 import org.apache.log4j.Logger;
32 import org.xml.sax.*;
33 import org.xml.sax.helpers.AttributesImpl JavaDoc;
34
35 import org.apache.axis.message.MessageElement;
36 import org.apache.axis.message.SOAPHeader;
37 import org.apache.axis.message.Text;
38
39 /**
40  */

41 public class SOAPDataEncoder implements ContentHandler {
42
43     /* Logger */
44     private static Logger mLogger = Logger.getLogger(SOAPDataEncoder.class);
45
46     /** Hint to allocate buffer size large enough to hold output. */
47     private int initsize = 0;
48     private static final int DEFAULT_BUFFER_SIZE = 4096;
49
50     /**
51      * The SWF file
52      */

53     private FlashOutput mSWF = null;
54     /**
55      * Size of the SWF file.
56      */

57     private long mSize = -1;
58
59     private int mSWFVersion;
60
61     /**
62      * Constructs an empty SOAPDataEncoder.
63      */

64     public SOAPDataEncoder (int swfversion) {
65         mSWFVersion = swfversion;
66     }
67
68     /**
69      * Workaround variable for bug 4680.
70      */

71     boolean isProgram = false;
72
73     /**
74      * Constructs a SOAPDataEncoder with a buffer allocation size hint.
75      * @param initsize hint to allocate buffer size large enough to hold output.
76      */

77     public SOAPDataEncoder (int swfversion, int initsize) {
78         this(swfversion);
79         this.initsize = initsize;
80     }
81
82     public SOAPDataEncoder(int swfversion, Vector v, SOAPHeader h) {
83         this(swfversion);
84         buildFromElements(v, h);
85     }
86
87     public SOAPDataEncoder(int swfversion, Program p, SOAPHeader h) {
88         this(swfversion);
89         buildFromProgram(p, h);
90     }
91
92     public SOAPDataEncoder buildFromFault(AxisFault fault) {
93         start();
94         String JavaDoc actor = fault.getFaultActor(); // can be null
95
QName JavaDoc code = fault.getFaultCode(); // can be null
96
String JavaDoc node = fault.getFaultNode(); // SOAP1.2
97
String JavaDoc reason = fault.getFaultReason(); // SOAP1.2==SOAP1.1 faultstring
98
String JavaDoc role = fault.getFaultRole(); // SOAP1.2==SOAP1.1 actor
99
String JavaDoc faultstring = fault.getFaultString(); // never null
100
QName JavaDoc[] subcodes = fault.getFaultSubCodes(); // can be null
101
// Element[] details = fault.getFaultDetails(); // can be null
102
// ArrayList headers = fault.getHeaders();
103

104         int count = 0;
105         if (subcodes != null) {
106             for (int i=0; i < subcodes.length; i++) {
107                 LZSOAPUtils.pushQName(program, subcodes[i], dc);
108             }
109             program.push(subcodes.length);
110             body.writeByte(Actions.InitArray);
111             count++;
112         }
113         if (actor != null) {
114             program.push("actor");
115             program.push(actor);
116             count++;
117         }
118         if (node != null) {
119             program.push("node");
120             program.push(node);
121             count++;
122         }
123         if (reason != null) {
124             program.push("reason");
125             program.push(reason);
126             count++;
127         }
128         if (role != null) {
129             program.push("role");
130             program.push(role);
131             count++;
132         }
133         if (faultstring != null) {
134             program.push("faultstring");
135             program.push(faultstring);
136             count++;
137         }
138         if (code != null) {
139             program.push("code");
140             LZSOAPUtils.pushQName(program, code, dc);
141             count++;
142         }
143
144         program.push("errortype");
145         program.push("fault");
146
147         program.push(count+1);
148         body.writeByte(Actions.InitObject);
149         end();
150
151         return this;
152     }
153
154     public SOAPDataEncoder buildFromException(Exception JavaDoc e) {
155         start();
156         {
157             Throwable JavaDoc cause = e.getCause();
158             String JavaDoc message = e.getMessage();
159             StringWriter sw = new StringWriter();
160             e.printStackTrace(new PrintWriter(sw));
161
162             int count = 3;
163             program.push("stacktrace");
164             program.push(sw.toString());
165
166             program.push("faultString");
167             if (message != null) {
168                 program.push(message);
169             } else {
170                 program.push(sw.toString());
171             }
172
173             if (cause != null) {
174                 program.push("cause");
175                 program.push(cause.toString());
176                 count++;
177             }
178
179             program.push("errortype");
180             program.push("exception");
181
182             program.push(count);
183
184             body.writeByte(Actions.InitObject);
185         }
186         end();
187         return this;
188     }
189
190     //============================================================
191
// SAX API
192
//============================================================
193

194     /**
195      * Receive notification of character data.
196      *
197      * @param ch the characters from the XML document.
198      * @param start the start position in the array.
199      * @param length the number of characters to read from the array.
200      *
201      * @see #characters(String) characters(String)
202      */

203     public void characters(char[] ch, int start, int length) {
204         String JavaDoc text = new String JavaDoc(ch, start, length);
205         characters(text);
206     }
207
208     /**
209      * Receive notification of string data.
210      *
211      * @param text the string from the XML document.
212      *
213      * @see #characters(char[], int, int) characters(char[], int, int)
214      */

215     public void characters(String JavaDoc text) {
216         // makeTextNode = function (text, parent)
217
// dup pointer to parent (who is at top of stack)
218
// DUP
219
body.writeByte(Actions.PushDuplicate);
220         // Push text
221

222
223         body.writeByte(Actions.PushData);
224         // Leave a two byte space for the PUSH length
225
// Mark where we are, so we can rewrite this with correct length later.
226
int push_bufferpos = body.getPos();
227         body.writeWord(0); // placeholder 16-bit length field
228

229         DataCommon.pushMergedStringData(text, body, dc);
230         // Set up argsnum and function name
231
// PUSH 2, _tdn
232
body.writeByte(0x07); // INT type
233
body.writeDWord(2); // '2' integer constant ; number of args to function
234
body.writeByte(0x08); // SHORT DICTIONARY LOOKUP
235
body.writeByte(textnode_idx); // push function name: index of "_t" string constant
236

237         // Now go back and fix up the size arg to the PUSH instruction
238
int total_size = body.getPos() - (push_bufferpos + 2);
239         //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
240
body.writeWordAt(total_size, push_bufferpos);
241
242         body.writeByte(Actions.CallFunction);
243         // Pop the node, because there will be no end tag for it, and it has no children.
244
body.writeByte(Actions.Pop);
245     }
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 JavaDoc uri, String JavaDoc localName, String JavaDoc 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     /**
278      * End the scope of a prefix-URI mapping. This method is unimplemented.
279      *
280      * @param prefix the prefix that was being mapped.
281      */

282     public void endPrefixMapping(String JavaDoc prefix) {
283     }
284
285     /**
286      * Receive notification of ignorable whitespace in element content. This
287      * method is unimplemented.
288      *
289      * @param ch the characters from the XML document.
290      * @param start the start position in the array.
291      * @param length the number of characters to read from the array.
292      */

293     public void ignorableWhitespace(char[] ch, int start, int length) {
294     }
295
296     /**
297      * Receive notification of a processing instruction. This method is
298      * unimplemented.
299      *
300      * @param target the processing instruction target.
301      * @param data the processing instruction data, or null if none was
302      * supplied. The data does not include any whitespace separating it from the
303      * target.
304      */

305     public void processingInstruction(String JavaDoc target, String JavaDoc data) {
306     }
307
308     /**
309      * Receive an object for locating the origin of SAX document events. This
310      * method is unimplemented.
311      *
312      * @param locator an object that can return the location of any SAX document
313      * event.
314      */

315     public void setDocumentLocator(Locator locator) {
316     }
317
318     /**
319      * Receive notification of a skipped entity. This method is unimplemented.
320      *
321      * @param name the name of the skipped entity. If it is a parameter entity,
322      * the name will begin with '%', and if it is the external DTD subset, it
323      * will be the string "[dtd]".
324      */

325     public void skippedEntity(String JavaDoc name) {
326     }
327
328     /** State vars */
329     DataContext dc;
330     // The node constructor function name.
331
private byte constructor_idx;
332     private byte textnode_idx;
333
334     FlashBuffer body;
335     Program program;
336     Program resultProgram;
337     FlashBuffer out;
338
339     public void start() {
340         // Room at the end of the buffer for maybe some callback code to the runtime to say we're done.
341
// Allocate enough room to hold the data nodes and strings ; it should be < input XML filesize
342
body = new FlashBuffer(initsize == 0 ? DEFAULT_BUFFER_SIZE : 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(DataCommon.NODE_INSTANTIATOR_FN, dc) & 0xFF);
385         textnode_idx = (byte) (DataCommon.addStringConstant(DataCommon.TEXT_INSTANTIATOR_FN, dc) & 0xFF);
386
387         // Bind the node creation functions to some short local names:
388
// element nodes: _root._m => _m
389
program.push(new Object JavaDoc[]{"_m", "_root"});
390         program.getVar();
391         program.push("_m");
392         body.writeByte(Actions.GetMember);
393         program.setVar();
394
395         // text nodes: _root._t => _t
396
program.push(new Object JavaDoc[]{"_t", "_root"});
397         program.getVar();
398         program.push("_t");
399         body.writeByte(Actions.GetMember);
400         program.setVar();
401     }
402
403     /**
404      * Receive notification of the end of a document.
405      *
406      * @see #startDocument() startDocument()
407      */

408     public void endDocument() {
409         end();
410     }
411
412     /**
413      * @return the Flash program to build the data set.
414      */

415     private Program getProgram() {
416         return resultProgram;
417     }
418
419
420     /**
421      * Make SWF
422      *
423      * @return FlashFile containing entire SWF with header
424      */

425     private FlashFile makeSWFFile() {
426         // Create FlashFile object nd include action bytes
427
FlashFile file = FlashFile.newFlashFile();
428         Script s = new Script(1);
429         file.setMainScript(s);
430         file.setVersion(mSWFVersion);
431         Frame frame = s.newFrame();
432         frame.addFlashObject(new DoAction(resultProgram));
433         return file;
434     }
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()
446         throws IOException {
447
448         generate();
449         return mSWF.getInputStream();
450     }
451
452     /**
453      * Return the size of the output object; only valid after endDocument
454      * {@link #endDocument endDocument()} has been called. Must not be called
455      * before {@link #endDocument endDocument()}.
456      *
457      * @return long representing the size
458      */

459     public long getSize()
460         throws IOException {
461
462         generate();
463         return mSize;
464     }
465
466     /**
467      * Generate the SWF file
468      */

469     private void generate() throws IOException {
470
471         if (mSWF == null) {
472             try {
473                 InputStream input;
474                 FlashFile file = makeSWFFile();
475                 mSWF = file.generate();
476                 mSize = mSWF.pos;
477             } catch (IVException ex) {
478                 throw new ChainedException(ex);
479             }
480         }
481     }
482
483     /**
484      * A lower level call than startElement(); attributes must be supplied by
485      * individual calls to addAttribute(). This method is unimplemented.
486      * @param localName the element name.
487      */

488     public void _startElement (String JavaDoc localName) {
489
490     }
491
492     /**
493      * A low level call to add an attribute, must be preceded by call to
494      * _startElement() for a given element. This method is unimplemented.
495      */

496     public void addAttribute (String JavaDoc attrName, String JavaDoc attrVal) {
497
498     }
499
500
501     /**
502      * Receive notification of the beginning of an element. This method is
503      * equivalent to calling {@link #startElement(String, Attributes)
504      * startElement(String, Attributes)} -- the uri and qName parameters are
505      * ignored.
506      *
507      * @param uri the Namespace URI, or the empty string if the element has no
508      * Namespace URI or if Namespace processing is not being performed.
509      * @param localName the local name (without prefix), or the empty string if
510      * Namespace processing is not being performed.
511      * @param qName the qualified name (with prefix), or the empty string if
512      * qualified names are not available.
513      * @param atts the attributes attached to the element. If there are no
514      * attributes, it shall be an empty Attributes object.
515      *
516      * @see #startElement(String, Attributes) startElement(String, Attributes)
517      */

518     public void startElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName, Attributes atts) {
519         startElement(localName, atts);
520     }
521
522
523     /**
524      * Receive notification of the beginning of an element.
525      *
526      * @param localName the local name (without prefix), or the empty string if
527      * Namespace processing is not being performed.
528      * @param atts the attributes attached to the element. If there are no
529      * attributes, it shall be an empty Attributes object.
530      *
531      * @see #startElement(String, String, String, Attributes)
532      * startElement(String, String, String, Attributes)
533      */

534     public void startElement(String JavaDoc localName, Attributes atts) {
535         int idx; // tmp var to hold a string pool index
536
// makeNodeNoText = function (attrs, name, parent)
537
// dup pointer to PARENT (who is at top of stack)
538
// DUP
539
body.writeByte(Actions.PushDuplicate);
540
541         // We're really squeezing things down, so we are going to merge
542
// the PUSH of the element name with the PUSH of the attribute key/value
543
// data and the attribute count. So the stack will look like
544
// [eltname attrname1 attrval1 attrname2 attrval2 ... ... nattrs]
545
// when we're done
546
body.writeByte(Actions.PushData);
547         // Leave a two byte space for the PUSH length
548
// Mark where we are, so we can rewrite this with correct length later.
549
int push_bufferpos = body.getPos();
550         body.writeWord(0); // placeholder 16-bit length field
551

552         // Push element NAME
553
String JavaDoc eltname = localName;
554         DataCommon.pushMergedStringDataSymbol(eltname, body, dc);
555
556         // Fold all the attribute key/value pairs into a single PUSH
557
// Build ATTRIBUTE object
558
int nattrs = atts.getLength();
559
560         // PUSH := {0x96, lenlo, lenhi, 0x00, char, char, char, ...0x00, }
561
for (int i = 0; i < nattrs; i++) {
562             String JavaDoc attrname = atts.getLocalName(i);
563             //System.out.print("Attr " + attrname);
564
DataCommon.pushMergedStringDataSymbol(attrname, body, dc);
565
566             String JavaDoc attrval = atts.getValue(i);
567             //System.out.println("= " + attrval);
568
DataCommon.pushMergedStringData(attrval, body, dc);
569         }
570         // create the attrs object; push the attr count
571
body.writeByte(0x07); // INT type
572
body.writeDWord(nattrs);
573         // Now go back and fix up the size arg to the PUSH instruction
574
int total_size = body.getPos() - (push_bufferpos + 2);
575         //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
576
body.writeWordAt(total_size, push_bufferpos);
577         body.writeByte(Actions.InitObject);
578
579         // stack now has [parent, name, attrs]
580
// Push # of args and node-instantiator-function name
581
// PUSH 3, _mdn
582
// [PUSHDATA, 7, 0, 0x07, 03 00 00 00 0x08, constructor_idx]
583
body.writeByte(Actions.PushData);
584         body.writeWord(7);
585         body.writeByte(0x07); // INT type
586
body.writeDWord(3); // '3' integer constant , number of args to node constructor fn
587
body.writeByte(0x08); // SHORT DICTIONARY LOOKUP type
588
body.writeByte(constructor_idx); // index of "_m" string constant
589
body.writeByte(Actions.CallFunction);
590         // We leave the new node on the stack, so we can reference it as the parent
591
// Stack => [parentnode newnode]
592
}
593
594     /**
595      * Begin the scope of a prefix-URI Namespace mapping. This method is
596      * unimplemented.
597      *
598      * @param prefix the Namespace prefix being declared.
599      * @param uri the Namespace URI the prefix is mapped to.
600      */

601     public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) {
602     }
603
604
605     //============================================================
606
// End SAX ContentHandler Compatibility
607
//============================================================
608

609     private void buildHeaders(SOAPHeader h) {
610         int hCount = 0;
611         Iterator JavaDoc iter = h.getChildElements();
612         while (iter.hasNext()) {
613             hCount++;
614             traverseDOM((SOAPElement JavaDoc)iter.next());
615         }
616         if (hCount != 0) {
617             program.push(hCount);
618             body.writeByte(Actions.InitArray);
619         } else {
620             LZSOAPUtils.pushNull(body);
621         }
622     }
623
624     /**
625      * Build from a vector of SOAPElement items.
626      *
627      * @param v vector of SOAPElement items.
628      */

629     public void buildFromElements(Vector v, SOAPHeader h) {
630         startDocument();
631
632         // soap headers
633
buildHeaders(h);
634
635         // array of documents
636
for (int i = v.size()-1; i >= 0; --i) {
637             traverseDOM((SOAPElement JavaDoc)v.get(i));
638         }
639         program.push(v.size());
640         body.writeByte(Actions.InitArray);
641
642         endDocument();
643     }
644
645
646     /**
647      * Build from a vector of SOAPElement items.
648      *
649      * @param v vector of SOAPElement items.
650      */

651     public void buildFromProgram(Program p, SOAPHeader h) {
652         // Workaround variable for bug 4680
653
isProgram = true;
654
655         startDocument();
656
657         // soap headers
658
buildHeaders(h);
659
660         // program
661
if ( p == null ) {
662             LZSOAPUtils.pushNull(body);
663         } else {
664             body.writeFOB(p.body());
665         }
666
667         endDocument();
668     }
669
670
671     /**
672      * Traverse DOM for single SOAPElement.
673      */

674     private void traverseDOM(SOAPElement JavaDoc el) {
675         // Build a root node by calling the runtime's root node instantiator
676
//
677
// The root node will have $n = 0, and whatever other initial conditions are needed.
678
program.push(0); // Root node creator function takes no args.
679
program.push("_root");
680         program.getVar();
681         program.push(DataCommon.ROOT_NODE_INSTANTIATOR_FN);
682         program.callMethod();
683         // The root node is now on the stack.
684
// Build data. Invariant is that it leaves the stack the way it found it.
685

686         AttributesImpl JavaDoc emptyAttr = new AttributesImpl JavaDoc();
687 // startElement("resultset", emptyAttr);
688
// startElement("body", emptyAttr);
689

690         // we're now set up for a single document...let's traverse the element
691
_traverseDOM(el);
692
693 // endElement();
694
// endElement();
695

696         // The root node is sitting on the stack. Finalize; bind the variable
697
// "root" to the node that the lfc expects.
698
program.push(1);
699         program.push("_root");
700         program.getVar();
701         program.push(DataCommon.ROOT_NODE_FINAL_FN);
702         program.callMethod();
703     }
704
705
706     /**
707      * Help traverse the DOM. Start point is traverseDOM().
708      */

709     private void _traverseDOM(SOAPElement JavaDoc el) {
710         AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
711         Iterator JavaDoc iter = el.getAllAttributes();
712         while (iter.hasNext()) {
713             Name JavaDoc attrName = (Name JavaDoc)iter.next();
714             String JavaDoc attrValue = el.getAttributeValue(attrName);
715             attrs.addAttribute(attrName.getURI(), attrName.getLocalName(),
716                                attrName.getQualifiedName(), "",
717                                attrValue);
718         }
719
720         Name JavaDoc elName = el.getElementName();
721         startElement(elName.getLocalName(), attrs);
722
723         iter = el.getChildElements();
724         while (iter.hasNext()) {
725             Object JavaDoc o = iter.next();
726             if (Text.class.isInstance(o)) {
727                 // add text node
728
characters(((Text)o).getValue());
729             } else {
730                 SOAPElement JavaDoc child = (SOAPElement JavaDoc)o;
731                 _traverseDOM(child);
732             }
733         }
734
735
736         endElement();
737
738     }
739
740
741 }
742
Popular Tags