summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java
diff options
context:
space:
mode:
authorBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
committerBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
commitaedc34d55462a75e329bbf342251ff6504cd117e (patch)
treebcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java
Initial import from SVN
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.java526
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);
+ }
+
+ }
+
+}