Coverage Report - net.wotonomy.web.xml.XMLRPCEncoder
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLRPCEncoder
7% 
0% 
4
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Intersect Software Corporation
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 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  10
 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  9
     public static final Format DATEFORMAT8601 = 
 89  1
         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  
             //XMLWriter writer = new UTF8XMLWriter( anOutputStream );
 103  0
             RPCXMLWriter writer = new RPCXMLWriter(anOutputStream,OutputFormat.createCompactFormat());
 104  0
                 writeValueToXMLWriter( anObject, writer );
 105  0
             writer.flush();
 106  
         }
 107  0
         catch ( Exception exc )
 108  
         {
 109  0
             throw new WotonomyException( exc );
 110  0
         }
 111  0
     }
 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  9
             RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat());
 133  9
             writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
 134  9
             writer.startElement( METHODCALL );
 135  0
             writer.startElement( METHODNAME );
 136  0
             writer.write( aMethodName );
 137  0
             writer.endElement( METHODNAME );
 138  0
             writer.startElement( PARAMS );
 139  0
             for ( int i = 0; i < aParameterArray.length; i++ )
 140  
             {
 141  0
                 writer.startElement( PARAM );
 142  0
                 writeValueToXMLWriter( aParameterArray[i], writer );
 143  0
                 writer.endElement( PARAM );
 144  
             }
 145  0
             writer.endElement( PARAMS );
 146  0
             writer.endElement( METHODCALL );
 147  0
             writer.flush();
 148  
         }
 149  9
         catch ( Exception exc )
 150  
         {
 151  9
             throw new WotonomyException( exc );
 152  0
         }
 153  
         
 154  
         //TODO: should this return the content-length?
 155  0
     }
 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  0
             RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
 175  0
             writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
 176  0
             writer.startElement( METHODRESPONSE );
 177  0
             writer.startElement( PARAMS );
 178  0
             writer.startElement( PARAM );
 179  0
             writeValueToXMLWriter( aResult, writer );                
 180  0
             writer.endElement( PARAM );
 181  0
             writer.endElement( PARAMS );
 182  0
             writer.endElement( METHODRESPONSE );
 183  0
             writer.flush();
 184  
         }
 185  0
         catch ( Exception exc )
 186  
         {
 187  0
             throw new WotonomyException( exc );
 188  0
         }
 189  
         
 190  
         //TODO: should this return the content-length?
 191  0
     }
 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  0
             RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
 210  0
             writer.processingInstruction( "xml", "version=\"1.0\"" );
 211  0
             writer.startElement( METHODRESPONSE );
 212  0
             writer.startElement( FAULT );
 213  0
             writer.startElement( VALUE );
 214  0
             writer.startElement( STRUCT );
 215  
 
 216  0
             writer.startElement( MEMBER );
 217  0
             writer.startElement( NAME );
 218  0
             writer.write( FAULTCODE );
 219  0
             writer.endElement( NAME );
 220  0
             writer.startElement( VALUE );
 221  0
             writer.startElement( INT );
 222  0
             writer.write( new Integer( aFaultCode ).toString() );
 223  0
             writer.endElement( INT );
 224  0
             writer.endElement( VALUE );
 225  0
             writer.endElement( MEMBER );
 226  
 
 227  0
             writer.startElement( MEMBER );
 228  0
             writer.startElement( NAME );
 229  0
             writer.write( FAULTSTRING );
 230  0
             writer.endElement( NAME );
 231  0
             writer.startElement( VALUE );
 232  0
             writer.startElement( STRING );
 233  0
             writer.write( aFaultString );
 234  0
             writer.endElement( STRING );
 235  0
             writer.endElement( VALUE );
 236  0
             writer.endElement( MEMBER );
 237  
 
 238  0
             writer.endElement( STRUCT );
 239  0
             writer.endElement( VALUE );
 240  0
             writer.endElement( FAULT );
 241  0
             writer.endElement( METHODRESPONSE );
 242  0
             writer.flush();
 243  
         }
 244  0
         catch ( Exception exc )
 245  
         {
 246  0
             throw new WotonomyException( exc );
 247  0
         }
 248  
         
 249  
         //TODO: should this return the content-length?
 250  0
     }
 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  0
         if ( anObject == null )
 263  
         {
 264  0
                 writer.startElement( VALUE );
 265  
                 // write nil for null
 266  0
                 Element nill = new NonLazyElement(NIL);
 267  0
                 writer.write(nill);
 268  0
         }
 269  
         else
 270  0
         if ( anObject instanceof Collection )
 271  
         {
 272  
             // write class so we can restore if possible
 273  0
                 AttributesImpl a = new AttributesImpl();
 274  0
             a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 275  
             
 276  0
             writer.startElement( VALUE, a );
 277  
 
 278  
             // write items in the order we get them from the iterator
 279  
             
 280  0
             writer.startElement( ARRAY );
 281  0
             writer.startElement( DATA );
 282  0
             Iterator it = ((Collection)anObject).iterator();
 283  0
             while ( it.hasNext() )
 284  
             {
 285  0
                 writeValueToXMLWriter( it.next(), writer );
 286  0
             }
 287  0
             writer.endElement( DATA );
 288  0
             writer.endElement( ARRAY );
 289  
         
 290  0
         }
 291  
         else
 292  0
         if ( anObject instanceof Map )
 293  
         {
 294  0
                 AttributesImpl a = new AttributesImpl();
 295  0
             a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 296  
             
 297  0
             writer.startElement( VALUE, a );
 298  
 
 299  
             // write items in the order we get them from the iterator
 300  
             //FIXME: The method-based properties are being ignored!
 301  
             
 302  
             Map.Entry entry;
 303  0
             writer.startElement( STRUCT );
 304  0
             writer.startElement( MEMBER );
 305  0
             Iterator it = ((Map)anObject).entrySet().iterator();
 306  0
             while ( it.hasNext() )
 307  
             {
 308  0
                 entry = (Map.Entry) it.next();
 309  0
                 writer.startElement( NAME );
 310  0
                 writeValueToXMLWriter( entry.getKey(), writer );
 311  0
                 writer.endElement( NAME );
 312  0
                 writeValueToXMLWriter( entry.getValue(), writer );
 313  0
             }
 314  0
             writer.endElement( MEMBER );
 315  0
             writer.endElement( STRUCT );
 316  0
         }
 317  
         else // not a collection 
 318  
         {
 319  
             // check for primitive types
 320  0
             if ( anObject instanceof String )
 321  
             {                
 322  0
                 writer.startElement( VALUE );
 323  
                 
 324  0
                 writer.startElement( STRING );
 325  0
                 writer.write( anObject.toString() );
 326  0
                 writer.endElement( STRING );
 327  0
             }
 328  
             else
 329  0
             if ( anObject instanceof StringBuffer )
 330  
             {
 331  
                 // write class so we can restore if possible
 332  0
                     AttributesImpl a = new AttributesImpl();
 333  0
                 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 334  
                 
 335  0
                 writer.startElement( VALUE, a );
 336  
 
 337  0
                 writer.startElement( STRING );
 338  0
                 writer.write( anObject.toString() );
 339  0
                 writer.endElement( STRING );
 340  0
             }
 341  
             else
 342  0
             if ( anObject instanceof Number )
 343  
             {
 344  
                 // write class so we can restore if possible
 345  0
                     AttributesImpl a = new AttributesImpl();
 346  0
                 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 347  
                 
 348  0
                 writer.startElement( VALUE, a );
 349  
 
 350  0
                 if ( ( anObject instanceof Double ) 
 351  0
                 || ( anObject instanceof Float ) )
 352  
                 {
 353  0
                     writer.startElement( DOUBLE );
 354  0
                     writer.write( anObject.toString() );
 355  0
                     writer.endElement( DOUBLE );
 356  0
                 }
 357  
                 else
 358  
                 {
 359  0
                     writer.startElement( INT );
 360  0
                     writer.write( anObject.toString() );
 361  0
                     writer.endElement( INT );
 362  
                 }
 363  0
             }
 364  
             else
 365  0
             if ( anObject instanceof Date )
 366  
             {
 367  
                 // write class so we can restore if possible
 368  0
                     AttributesImpl a = new AttributesImpl();
 369  0
                 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 370  
                 
 371  0
                 writer.startElement( VALUE, a );
 372  
 
 373  0
                 writer.startElement( DATE );
 374  0
                 writer.write( DATEFORMAT8601.format( anObject ) );
 375  0
                 writer.endElement( DATE );
 376  0
             }
 377  
             else
 378  0
             if ( anObject instanceof Boolean )
 379  
             {
 380  0
                 writer.startElement( BOOLEAN );
 381  0
                 if ( ((Boolean)anObject).booleanValue() )
 382  
                 {
 383  0
                     writer.write( "1" );
 384  0
                 }
 385  
                 else
 386  
                 {
 387  0
                     writer.write( "0" );
 388  
                 }
 389  0
                 writer.endElement( BOOLEAN );
 390  0
             }
 391  
             else
 392  0
             if ( anObject.getClass().isArray() )
 393  
             {
 394  
                 // write class so we can restore if possible
 395  0
                     AttributesImpl a = new AttributesImpl();
 396  0
                 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 397  
                 
 398  0
                 writer.startElement( VALUE, a );
 399  
     
 400  0
                 writer.startElement( ARRAY );
 401  0
                 writer.startElement( DATA );
 402  
     
 403  0
                 int length = Array.getLength( anObject );
 404  0
                 for ( int i = 0; i < length; i++ )
 405  
                 {
 406  0
                     writeValueToXMLWriter( Array.get( anObject, i ), writer );  
 407  
                 }
 408  
     
 409  0
                 writer.endElement( DATA );
 410  0
                 writer.endElement( ARRAY );
 411  0
             }
 412  
             else // not primitive or collection, treat as struct
 413  
             {
 414  
                 // write class so we can restore if possible
 415  0
                     AttributesImpl a = new AttributesImpl();
 416  0
                 a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
 417  
                 
 418  0
                 writer.startElement( VALUE, a );
 419  
 
 420  0
                 List readProperties = new ArrayList();
 421  0
                 String[] read = Introspector.getReadPropertiesForObject( anObject );
 422  0
                 for ( int i = 0; i < read.length; i++ )
 423  
                 {
 424  0
                     readProperties.add( read[i] );
 425  
                 }
 426  
         
 427  0
                 List properties = new ArrayList();
 428  0
                 String[] write = Introspector.getWritePropertiesForObject( anObject );
 429  0
                 for ( int i = 0; i < write.length; i++ )
 430  
                 {
 431  0
                     properties.add( write[i] );
 432  
                 }
 433  
                 
 434  
                 // only use readable properties
 435  0
                 properties.retainAll( readProperties );
 436  
                 
 437  
 //                if ( properties.size() > 0 )
 438  
 //                {
 439  
                     String key;
 440  
                     Object value;
 441  0
                     Iterator it = properties.iterator();
 442  0
                     writer.startElement( STRUCT );
 443  0
                     while ( it.hasNext() )
 444  
                     {
 445  0
                         key = (String) it.next();
 446  0
                         value = Introspector.get( anObject, key );
 447  
                     
 448  0
                         writer.startElement( MEMBER );
 449  0
                             writer.startElement( NAME );
 450  0
                                 writer.write( key ); 
 451  0
                             writer.endElement( NAME );
 452  0
                                 writeValueToXMLWriter( value, writer );
 453  0
                         writer.endElement( MEMBER );
 454  0
                     }
 455  0
                     writer.endElement( STRUCT );
 456  
 /*                    
 457  
                 }
 458  
                 else // no properties - write a converted string
 459  
                 {
 460  
                     writer.startElement( STRING );
 461  
                     Object converted =
 462  
                         ValueConverter.convertObjectToClass( anObject, String.class );
 463  
                     if ( converted != null )
 464  
                     {
 465  
                         writer.write( converted.toString() );
 466  
                     }
 467  
                     else
 468  
                     {
 469  
                         writer.write( anObject.toString() );
 470  
                     }
 471  
                     writer.endElement( STRING );
 472  
                 }
 473  
 */                
 474  
             }
 475  
         }
 476  
         
 477  0
         writer.endElement( VALUE );
 478  
         } 
 479  0
         catch ( Exception exc )
 480  
         {
 481  0
             System.err.println( "XMLFileSoup.writeValueToXMLWriter: " + exc );
 482  0
             exc.printStackTrace();
 483  0
         }
 484  
         
 485  0
     }
 486  
 /*    
 487  
     public static void main( String[] argv )
 488  
     {
 489  
         System.out.println( "<test>" );
 490  
         XMLRPCEncoder encoder = new XMLRPCEncoder();
 491  
         encoder.encodeRequest( "systemObject.test", new Object[] { 
 492  
             new net.wotonomy.test.TestObject(), 
 493  
             new net.wotonomy.test.TestObject(), 
 494  
             new net.wotonomy.test.TestObject() }, System.out );
 495  
         System.out.println();
 496  
         System.out.println();
 497  
         encoder.encodeResponse( new net.wotonomy.test.TestObject(), System.out );
 498  
         System.out.println();
 499  
         System.out.println();
 500  
         encoder.encodeFault( -1, "This is a fault.", System.out );
 501  
         System.out.println();
 502  
         System.out.println();
 503  
         System.out.println( "</test>" );
 504  
     }
 505  
 */        
 506  
     
 507  8
     private class RPCXMLWriter extends XMLWriter {
 508  
 
 509  8
                 public RPCXMLWriter(OutputStream arg0, OutputFormat arg1) throws UnsupportedEncodingException {
 510  9
                         super(arg0, arg1);
 511  9
                 }
 512  
 
 513  
                 public void endElement(String localname) throws SAXException {        
 514  0
                         super.endElement(null, localname, null);
 515  0
                 }
 516  
 
 517  
                 public void startElement(String localname) throws SAXException {
 518  9
                         this.startElement(localname, null);
 519  0
                 }
 520  
                 public void startElement(String localname, Attributes attributes) throws SAXException {
 521  9
                         this.startElement(null, localname, null, attributes);
 522  0
                 }
 523  
 
 524  
     }
 525  
     
 526  
 }