diff options
Diffstat (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java')
| -rw-r--r-- | projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java new file mode 100644 index 0000000..3a63d45 --- /dev/null +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java @@ -0,0 +1,526 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.web.xml; + +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.text.Format; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.WotonomyException; +import net.wotonomy.foundation.xml.XMLEncoder; + +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.dom4j.util.NonLazyElement; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** +* An implementation of XMLEncoder that serializes objects +* into XMLRPC format, which is found at http://xmlrpc.com/spec. +* We extend that standard only in that we add a "class" +* attribute to the "value" tag, so that a java-based decoder +* can more closely reconstruct the original java data structure. +* The class attribute can be safely ignored by clients. +* This implementation is not thread-safe, so a new instances +* should be created to accomodate multiple threads. +*/ +public class XMLRPCEncoder implements XMLEncoder +{ + public static final String METHODCALL = "methodCall"; + public static final String METHODNAME = "methodName"; + public static final String METHODRESPONSE = "methodResponse"; + public static final String PARAMS = "params"; + public static final String PARAM = "param"; + public static final String FAULT = "fault"; + public static final String FAULTCODE = "faultCode"; + public static final String FAULTSTRING = "faultString"; + + public static final String VALUE = "value"; + public static final String CLASS = "class"; + + public static final String STRUCT = "struct"; + public static final String MEMBER = "member"; + public static final String NAME = "name"; + + public static final String ARRAY = "array"; + public static final String DATA = "data"; + + public static final String NIL = "nil"; + public static final String INT = "int"; + public static final String I4 = "i4"; + public static final String BOOLEAN = "boolean"; + public static final String STRING = "string"; + public static final String DOUBLE = "double"; + public static final String DATE = "dateTime.iso8601"; + public static final String BASE64 = "base64"; + + public static final String TRUE = "1"; + public static final String FALSE = "0"; + + public static final Format DATEFORMAT8601 = + new SimpleDateFormat( "yyyyMMdd'T'HHmmss" ); + + /** + * Encodes an object to the specified output stream as XML. + * @param anObject The object to be serialized to XML format. + * @param anOutputStream The output stream to which the object + * will be written. + */ + public void encode( Object anObject, OutputStream anOutputStream ) + { + try + { + + //XMLWriter writer = new UTF8XMLWriter( anOutputStream ); + RPCXMLWriter writer = new RPCXMLWriter(anOutputStream,OutputFormat.createCompactFormat()); + writeValueToXMLWriter( anObject, writer ); + writer.flush(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + } + + /** + * Encodes a method request in XML-RPC format in a "methodCall" tag, + * and writes the XML to the specified output stream. + * This method only writes XML: the caller is responsible for + * generating the appropriate header, if any, which should set + * the content type as "text/xml" and the content length as appropriate. + * The caller is also responsible for writing the xml version tag. + * @param aMethodName The method name to appear in the "methodName" tag. + * @param aParameterArray An array of objects, each of which will be + * encoded as values enclosed in a "param" tag, all of which will be + * enclosed in a "params" tag. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeRequest( + String aMethodName, Object[] aParameterArray, + OutputStream anOutputStream ) + { + try + { + RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat()); + writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ); + writer.startElement( METHODCALL ); + writer.startElement( METHODNAME ); + writer.write( aMethodName ); + writer.endElement( METHODNAME ); + writer.startElement( PARAMS ); + for ( int i = 0; i < aParameterArray.length; i++ ) + { + writer.startElement( PARAM ); + writeValueToXMLWriter( aParameterArray[i], writer ); + writer.endElement( PARAM ); + } + writer.endElement( PARAMS ); + writer.endElement( METHODCALL ); + writer.flush(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + + //TODO: should this return the content-length? + } + + /** + * Encodes a method response in XML-RPC format in a "methodResponse" tag, + * and writes the XML to the specified output stream. + * This method only writes XML: the caller is responsible for + * generating the appropriate header, if any, which should set + * the content type as "text/xml" and the content length as appropriate. + * The caller is also responsible for writing the xml version tag. + * @param aResult A object which will be + * encoded as values enclosed in a "param" tag, all of which will be + * enclosed in a "params" tag. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeResponse( + Object aResult, OutputStream anOutputStream ) + { + try + { + RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() ); + writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ); + writer.startElement( METHODRESPONSE ); + writer.startElement( PARAMS ); + writer.startElement( PARAM ); + writeValueToXMLWriter( aResult, writer ); + writer.endElement( PARAM ); + writer.endElement( PARAMS ); + writer.endElement( METHODRESPONSE ); + writer.flush(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + + //TODO: should this return the content-length? + } + + /** + * Encodes a fault response in XML-RPC format in a "methodResponse" tag, + * and writes the XML to the specified output stream. + * This method only writes XML: the caller is responsible for first + * generating the appropriate header, if any, which should set + * the content type as "text/xml" and the content length as appropriate. + * The caller is also responsible for writing the xml version tag. + * @param aFaultCode An application-defined error code. + * @param aFaultString A human-readable error description. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeFault( + int aFaultCode, String aFaultString, OutputStream anOutputStream ) + { + try + { + RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() ); + writer.processingInstruction( "xml", "version=\"1.0\"" ); + writer.startElement( METHODRESPONSE ); + writer.startElement( FAULT ); + writer.startElement( VALUE ); + writer.startElement( STRUCT ); + + writer.startElement( MEMBER ); + writer.startElement( NAME ); + writer.write( FAULTCODE ); + writer.endElement( NAME ); + writer.startElement( VALUE ); + writer.startElement( INT ); + writer.write( new Integer( aFaultCode ).toString() ); + writer.endElement( INT ); + writer.endElement( VALUE ); + writer.endElement( MEMBER ); + + writer.startElement( MEMBER ); + writer.startElement( NAME ); + writer.write( FAULTSTRING ); + writer.endElement( NAME ); + writer.startElement( VALUE ); + writer.startElement( STRING ); + writer.write( aFaultString ); + writer.endElement( STRING ); + writer.endElement( VALUE ); + writer.endElement( MEMBER ); + + writer.endElement( STRUCT ); + writer.endElement( VALUE ); + writer.endElement( FAULT ); + writer.endElement( METHODRESPONSE ); + writer.flush(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + + //TODO: should this return the content-length? + } + + /** + * Performs the actual writing of the file to XML. + */ + private void writeValueToXMLWriter( + Object anObject, RPCXMLWriter writer ) + { + try + { + + + if ( anObject == null ) + { + writer.startElement( VALUE ); + // write nil for null + Element nill = new NonLazyElement(NIL); + writer.write(nill); + } + else + if ( anObject instanceof Collection ) + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + // write items in the order we get them from the iterator + + writer.startElement( ARRAY ); + writer.startElement( DATA ); + Iterator it = ((Collection)anObject).iterator(); + while ( it.hasNext() ) + { + writeValueToXMLWriter( it.next(), writer ); + } + writer.endElement( DATA ); + writer.endElement( ARRAY ); + + } + else + if ( anObject instanceof Map ) + { + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + // write items in the order we get them from the iterator + //FIXME: The method-based properties are being ignored! + + Map.Entry entry; + writer.startElement( STRUCT ); + writer.startElement( MEMBER ); + Iterator it = ((Map)anObject).entrySet().iterator(); + while ( it.hasNext() ) + { + entry = (Map.Entry) it.next(); + writer.startElement( NAME ); + writeValueToXMLWriter( entry.getKey(), writer ); + writer.endElement( NAME ); + writeValueToXMLWriter( entry.getValue(), writer ); + } + writer.endElement( MEMBER ); + writer.endElement( STRUCT ); + } + else // not a collection + { + // check for primitive types + if ( anObject instanceof String ) + { + writer.startElement( VALUE ); + + writer.startElement( STRING ); + writer.write( anObject.toString() ); + writer.endElement( STRING ); + } + else + if ( anObject instanceof StringBuffer ) + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + writer.startElement( STRING ); + writer.write( anObject.toString() ); + writer.endElement( STRING ); + } + else + if ( anObject instanceof Number ) + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + if ( ( anObject instanceof Double ) + || ( anObject instanceof Float ) ) + { + writer.startElement( DOUBLE ); + writer.write( anObject.toString() ); + writer.endElement( DOUBLE ); + } + else + { + writer.startElement( INT ); + writer.write( anObject.toString() ); + writer.endElement( INT ); + } + } + else + if ( anObject instanceof Date ) + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + writer.startElement( DATE ); + writer.write( DATEFORMAT8601.format( anObject ) ); + writer.endElement( DATE ); + } + else + if ( anObject instanceof Boolean ) + { + writer.startElement( BOOLEAN ); + if ( ((Boolean)anObject).booleanValue() ) + { + writer.write( "1" ); + } + else + { + writer.write( "0" ); + } + writer.endElement( BOOLEAN ); + } + else + if ( anObject.getClass().isArray() ) + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + writer.startElement( ARRAY ); + writer.startElement( DATA ); + + int length = Array.getLength( anObject ); + for ( int i = 0; i < length; i++ ) + { + writeValueToXMLWriter( Array.get( anObject, i ), writer ); + } + + writer.endElement( DATA ); + writer.endElement( ARRAY ); + } + else // not primitive or collection, treat as struct + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); + + writer.startElement( VALUE, a ); + + List readProperties = new ArrayList(); + String[] read = Introspector.getReadPropertiesForObject( anObject ); + for ( int i = 0; i < read.length; i++ ) + { + readProperties.add( read[i] ); + } + + List properties = new ArrayList(); + String[] write = Introspector.getWritePropertiesForObject( anObject ); + for ( int i = 0; i < write.length; i++ ) + { + properties.add( write[i] ); + } + + // only use readable properties + properties.retainAll( readProperties ); + +// if ( properties.size() > 0 ) +// { + String key; + Object value; + Iterator it = properties.iterator(); + writer.startElement( STRUCT ); + while ( it.hasNext() ) + { + key = (String) it.next(); + value = Introspector.get( anObject, key ); + + writer.startElement( MEMBER ); + writer.startElement( NAME ); + writer.write( key ); + writer.endElement( NAME ); + writeValueToXMLWriter( value, writer ); + writer.endElement( MEMBER ); + } + writer.endElement( STRUCT ); +/* + } + else // no properties - write a converted string + { + writer.startElement( STRING ); + Object converted = + ValueConverter.convertObjectToClass( anObject, String.class ); + if ( converted != null ) + { + writer.write( converted.toString() ); + } + else + { + writer.write( anObject.toString() ); + } + writer.endElement( STRING ); + } +*/ + } + } + + writer.endElement( VALUE ); + } + catch ( Exception exc ) + { + System.err.println( "XMLFileSoup.writeValueToXMLWriter: " + exc ); + exc.printStackTrace(); + } + + } +/* + public static void main( String[] argv ) + { + System.out.println( "<test>" ); + XMLRPCEncoder encoder = new XMLRPCEncoder(); + encoder.encodeRequest( "systemObject.test", new Object[] { + new net.wotonomy.test.TestObject(), + new net.wotonomy.test.TestObject(), + new net.wotonomy.test.TestObject() }, System.out ); + System.out.println(); + System.out.println(); + encoder.encodeResponse( new net.wotonomy.test.TestObject(), System.out ); + System.out.println(); + System.out.println(); + encoder.encodeFault( -1, "This is a fault.", System.out ); + System.out.println(); + System.out.println(); + System.out.println( "</test>" ); + } +*/ + + private class RPCXMLWriter extends XMLWriter { + + public RPCXMLWriter(OutputStream arg0, OutputFormat arg1) throws UnsupportedEncodingException { + super(arg0, arg1); + } + + public void endElement(String localname) throws SAXException { + super.endElement(null, localname, null); + } + + public void startElement(String localname) throws SAXException { + this.startElement(localname, null); + } + public void startElement(String localname, Attributes attributes) throws SAXException { + this.startElement(null, localname, null, attributes); + } + + } + +} |
