1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.web.xml;
20
21 import java.io.OutputStream;
22 import java.io.UnsupportedEncodingException;
23 import java.lang.reflect.Array;
24 import java.text.Format;
25 import java.text.SimpleDateFormat;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Date;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32
33 import net.wotonomy.foundation.internal.Introspector;
34 import net.wotonomy.foundation.internal.WotonomyException;
35 import net.wotonomy.foundation.xml.XMLEncoder;
36
37 import org.dom4j.Element;
38 import org.dom4j.io.OutputFormat;
39 import org.dom4j.io.XMLWriter;
40 import org.dom4j.util.NonLazyElement;
41 import org.xml.sax.Attributes;
42 import org.xml.sax.SAXException;
43 import org.xml.sax.helpers.AttributesImpl;
44
45 /***
46 * An implementation of XMLEncoder that serializes objects
47 * into XMLRPC format, which is found at http://xmlrpc.com/spec.
48 * We extend that standard only in that we add a "class"
49 * attribute to the "value" tag, so that a java-based decoder
50 * can more closely reconstruct the original java data structure.
51 * The class attribute can be safely ignored by clients.
52 * This implementation is not thread-safe, so a new instances
53 * should be created to accomodate multiple threads.
54 */
55 public class XMLRPCEncoder implements XMLEncoder
56 {
57 public static final String METHODCALL = "methodCall";
58 public static final String METHODNAME = "methodName";
59 public static final String METHODRESPONSE = "methodResponse";
60 public static final String PARAMS = "params";
61 public static final String PARAM = "param";
62 public static final String FAULT = "fault";
63 public static final String FAULTCODE = "faultCode";
64 public static final String FAULTSTRING = "faultString";
65
66 public static final String VALUE = "value";
67 public static final String CLASS = "class";
68
69 public static final String STRUCT = "struct";
70 public static final String MEMBER = "member";
71 public static final String NAME = "name";
72
73 public static final String ARRAY = "array";
74 public static final String DATA = "data";
75
76 public static final String NIL = "nil";
77 public static final String INT = "int";
78 public static final String I4 = "i4";
79 public static final String BOOLEAN = "boolean";
80 public static final String STRING = "string";
81 public static final String DOUBLE = "double";
82 public static final String DATE = "dateTime.iso8601";
83 public static final String BASE64 = "base64";
84
85 public static final String TRUE = "1";
86 public static final String FALSE = "0";
87
88 public static final Format DATEFORMAT8601 =
89 new SimpleDateFormat( "yyyyMMdd'T'HHmmss" );
90
91 /***
92 * Encodes an object to the specified output stream as XML.
93 * @param anObject The object to be serialized to XML format.
94 * @param anOutputStream The output stream to which the object
95 * will be written.
96 */
97 public void encode( Object anObject, OutputStream anOutputStream )
98 {
99 try
100 {
101
102
103 RPCXMLWriter writer = new RPCXMLWriter(anOutputStream,OutputFormat.createCompactFormat());
104 writeValueToXMLWriter( anObject, writer );
105 writer.flush();
106 }
107 catch ( Exception exc )
108 {
109 throw new WotonomyException( exc );
110 }
111 }
112
113 /***
114 * Encodes a method request in XML-RPC format in a "methodCall" tag,
115 * and writes the XML to the specified output stream.
116 * This method only writes XML: the caller is responsible for
117 * generating the appropriate header, if any, which should set
118 * the content type as "text/xml" and the content length as appropriate.
119 * The caller is also responsible for writing the xml version tag.
120 * @param aMethodName The method name to appear in the "methodName" tag.
121 * @param aParameterArray An array of objects, each of which will be
122 * encoded as values enclosed in a "param" tag, all of which will be
123 * enclosed in a "params" tag.
124 * @param anOutputStream The stream to which the XML will be written.
125 */
126 public void encodeRequest(
127 String aMethodName, Object[] aParameterArray,
128 OutputStream anOutputStream )
129 {
130 try
131 {
132 RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat());
133 writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
134 writer.startElement( METHODCALL );
135 writer.startElement( METHODNAME );
136 writer.write( aMethodName );
137 writer.endElement( METHODNAME );
138 writer.startElement( PARAMS );
139 for ( int i = 0; i < aParameterArray.length; i++ )
140 {
141 writer.startElement( PARAM );
142 writeValueToXMLWriter( aParameterArray[i], writer );
143 writer.endElement( PARAM );
144 }
145 writer.endElement( PARAMS );
146 writer.endElement( METHODCALL );
147 writer.flush();
148 }
149 catch ( Exception exc )
150 {
151 throw new WotonomyException( exc );
152 }
153
154
155 }
156
157 /***
158 * Encodes a method response in XML-RPC format in a "methodResponse" tag,
159 * and writes the XML to the specified output stream.
160 * This method only writes XML: the caller is responsible for
161 * generating the appropriate header, if any, which should set
162 * the content type as "text/xml" and the content length as appropriate.
163 * The caller is also responsible for writing the xml version tag.
164 * @param aResult A object which will be
165 * encoded as values enclosed in a "param" tag, all of which will be
166 * enclosed in a "params" tag.
167 * @param anOutputStream The stream to which the XML will be written.
168 */
169 public void encodeResponse(
170 Object aResult, OutputStream anOutputStream )
171 {
172 try
173 {
174 RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
175 writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
176 writer.startElement( METHODRESPONSE );
177 writer.startElement( PARAMS );
178 writer.startElement( PARAM );
179 writeValueToXMLWriter( aResult, writer );
180 writer.endElement( PARAM );
181 writer.endElement( PARAMS );
182 writer.endElement( METHODRESPONSE );
183 writer.flush();
184 }
185 catch ( Exception exc )
186 {
187 throw new WotonomyException( exc );
188 }
189
190
191 }
192
193 /***
194 * Encodes a fault response in XML-RPC format in a "methodResponse" tag,
195 * and writes the XML to the specified output stream.
196 * This method only writes XML: the caller is responsible for first
197 * generating the appropriate header, if any, which should set
198 * the content type as "text/xml" and the content length as appropriate.
199 * The caller is also responsible for writing the xml version tag.
200 * @param aFaultCode An application-defined error code.
201 * @param aFaultString A human-readable error description.
202 * @param anOutputStream The stream to which the XML will be written.
203 */
204 public void encodeFault(
205 int aFaultCode, String aFaultString, OutputStream anOutputStream )
206 {
207 try
208 {
209 RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
210 writer.processingInstruction( "xml", "version=\"1.0\"" );
211 writer.startElement( METHODRESPONSE );
212 writer.startElement( FAULT );
213 writer.startElement( VALUE );
214 writer.startElement( STRUCT );
215
216 writer.startElement( MEMBER );
217 writer.startElement( NAME );
218 writer.write( FAULTCODE );
219 writer.endElement( NAME );
220 writer.startElement( VALUE );
221 writer.startElement( INT );
222 writer.write( new Integer( aFaultCode ).toString() );
223 writer.endElement( INT );
224 writer.endElement( VALUE );
225 writer.endElement( MEMBER );
226
227 writer.startElement( MEMBER );
228 writer.startElement( NAME );
229 writer.write( FAULTSTRING );
230 writer.endElement( NAME );
231 writer.startElement( VALUE );
232 writer.startElement( STRING );
233 writer.write( aFaultString );
234 writer.endElement( STRING );
235 writer.endElement( VALUE );
236 writer.endElement( MEMBER );
237
238 writer.endElement( STRUCT );
239 writer.endElement( VALUE );
240 writer.endElement( FAULT );
241 writer.endElement( METHODRESPONSE );
242 writer.flush();
243 }
244 catch ( Exception exc )
245 {
246 throw new WotonomyException( exc );
247 }
248
249
250 }
251
252 /***
253 * Performs the actual writing of the file to XML.
254 */
255 private void writeValueToXMLWriter(
256 Object anObject, RPCXMLWriter writer )
257 {
258 try
259 {
260
261
262 if ( anObject == null )
263 {
264 writer.startElement( VALUE );
265
266 Element nill = new NonLazyElement(NIL);
267 writer.write(nill);
268 }
269 else
270 if ( anObject instanceof Collection )
271 {
272
273 AttributesImpl a = new AttributesImpl();
274 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
275
276 writer.startElement( VALUE, a );
277
278
279
280 writer.startElement( ARRAY );
281 writer.startElement( DATA );
282 Iterator it = ((Collection)anObject).iterator();
283 while ( it.hasNext() )
284 {
285 writeValueToXMLWriter( it.next(), writer );
286 }
287 writer.endElement( DATA );
288 writer.endElement( ARRAY );
289
290 }
291 else
292 if ( anObject instanceof Map )
293 {
294 AttributesImpl a = new AttributesImpl();
295 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
296
297 writer.startElement( VALUE, a );
298
299
300
301
302 Map.Entry entry;
303 writer.startElement( STRUCT );
304 writer.startElement( MEMBER );
305 Iterator it = ((Map)anObject).entrySet().iterator();
306 while ( it.hasNext() )
307 {
308 entry = (Map.Entry) it.next();
309 writer.startElement( NAME );
310 writeValueToXMLWriter( entry.getKey(), writer );
311 writer.endElement( NAME );
312 writeValueToXMLWriter( entry.getValue(), writer );
313 }
314 writer.endElement( MEMBER );
315 writer.endElement( STRUCT );
316 }
317 else
318 {
319
320 if ( anObject instanceof String )
321 {
322 writer.startElement( VALUE );
323
324 writer.startElement( STRING );
325 writer.write( anObject.toString() );
326 writer.endElement( STRING );
327 }
328 else
329 if ( anObject instanceof StringBuffer )
330 {
331
332 AttributesImpl a = new AttributesImpl();
333 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
334
335 writer.startElement( VALUE, a );
336
337 writer.startElement( STRING );
338 writer.write( anObject.toString() );
339 writer.endElement( STRING );
340 }
341 else
342 if ( anObject instanceof Number )
343 {
344
345 AttributesImpl a = new AttributesImpl();
346 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
347
348 writer.startElement( VALUE, a );
349
350 if ( ( anObject instanceof Double )
351 || ( anObject instanceof Float ) )
352 {
353 writer.startElement( DOUBLE );
354 writer.write( anObject.toString() );
355 writer.endElement( DOUBLE );
356 }
357 else
358 {
359 writer.startElement( INT );
360 writer.write( anObject.toString() );
361 writer.endElement( INT );
362 }
363 }
364 else
365 if ( anObject instanceof Date )
366 {
367
368 AttributesImpl a = new AttributesImpl();
369 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
370
371 writer.startElement( VALUE, a );
372
373 writer.startElement( DATE );
374 writer.write( DATEFORMAT8601.format( anObject ) );
375 writer.endElement( DATE );
376 }
377 else
378 if ( anObject instanceof Boolean )
379 {
380 writer.startElement( BOOLEAN );
381 if ( ((Boolean)anObject).booleanValue() )
382 {
383 writer.write( "1" );
384 }
385 else
386 {
387 writer.write( "0" );
388 }
389 writer.endElement( BOOLEAN );
390 }
391 else
392 if ( anObject.getClass().isArray() )
393 {
394
395 AttributesImpl a = new AttributesImpl();
396 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
397
398 writer.startElement( VALUE, a );
399
400 writer.startElement( ARRAY );
401 writer.startElement( DATA );
402
403 int length = Array.getLength( anObject );
404 for ( int i = 0; i < length; i++ )
405 {
406 writeValueToXMLWriter( Array.get( anObject, i ), writer );
407 }
408
409 writer.endElement( DATA );
410 writer.endElement( ARRAY );
411 }
412 else
413 {
414
415 AttributesImpl a = new AttributesImpl();
416 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
417
418 writer.startElement( VALUE, a );
419
420 List readProperties = new ArrayList();
421 String[] read = Introspector.getReadPropertiesForObject( anObject );
422 for ( int i = 0; i < read.length; i++ )
423 {
424 readProperties.add( read[i] );
425 }
426
427 List properties = new ArrayList();
428 String[] write = Introspector.getWritePropertiesForObject( anObject );
429 for ( int i = 0; i < write.length; i++ )
430 {
431 properties.add( write[i] );
432 }
433
434
435 properties.retainAll( readProperties );
436
437
438
439 String key;
440 Object value;
441 Iterator it = properties.iterator();
442 writer.startElement( STRUCT );
443 while ( it.hasNext() )
444 {
445 key = (String) it.next();
446 value = Introspector.get( anObject, key );
447
448 writer.startElement( MEMBER );
449 writer.startElement( NAME );
450 writer.write( key );
451 writer.endElement( NAME );
452 writeValueToXMLWriter( value, writer );
453 writer.endElement( MEMBER );
454 }
455 writer.endElement( STRUCT );
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474 }
475 }
476
477 writer.endElement( VALUE );
478 }
479 catch ( Exception exc )
480 {
481 System.err.println( "XMLFileSoup.writeValueToXMLWriter: " + exc );
482 exc.printStackTrace();
483 }
484
485 }
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507 private class RPCXMLWriter extends XMLWriter {
508
509 public RPCXMLWriter(OutputStream arg0, OutputFormat arg1) throws UnsupportedEncodingException {
510 super(arg0, arg1);
511 }
512
513 public void endElement(String localname) throws SAXException {
514 super.endElement(null, localname, null);
515 }
516
517 public void startElement(String localname) throws SAXException {
518 this.startElement(localname, null);
519 }
520 public void startElement(String localname, Attributes attributes) throws SAXException {
521 this.startElement(null, localname, null, attributes);
522 }
523
524 }
525
526 }