From 2eb1f58dba205ab26f3cde09d5b6672c4331fcd4 Mon Sep 17 00:00:00 2001 From: "Eric J. Bowersox" Date: Wed, 16 Jul 2003 21:25:00 +0000 Subject: [PATCH] moved the serialization and deserialization of XML-RPC values into its own class, 'cos we're going to write a client implementation eventually, and it can share this code --- .../dynamo/xmlrpc/XmlRpcRequest.java | 322 +------ .../dynamo/xmlrpc/XmlRpcResult.java | 532 +---------- .../dynamo/xmlrpc/XmlRpcSerializer.java | 868 ++++++++++++++++++ 3 files changed, 873 insertions(+), 849 deletions(-) create mode 100644 src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcSerializer.java diff --git a/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcRequest.java b/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcRequest.java index 214f0ca..4f06405 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcRequest.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcRequest.java @@ -20,12 +20,9 @@ package com.silverwrist.dynamo.xmlrpc; import java.io.*; import java.text.*; import java.util.*; -import javax.mail.*; -import javax.mail.internet.*; import javax.servlet.*; import javax.servlet.http.*; import org.w3c.dom.*; -import com.silverwrist.util.*; import com.silverwrist.util.xml.*; import com.silverwrist.dynamo.RequestType; import com.silverwrist.dynamo.Verb; @@ -42,27 +39,6 @@ import com.silverwrist.dynamo.util.*; */ class XmlRpcRequest extends BaseDelegatingServiceProvider implements Request { - /*-------------------------------------------------------------------------------- - * Static data members - *-------------------------------------------------------------------------------- - */ - - private static final String EMPTY_STRING = ""; - - // Indicates the types of values associated with the request. - private static final int ITYP_INTEGER = 0; // integer - private static final int ITYP_BOOLEAN = 1; // Boolean - private static final int ITYP_STRING = 2; // string - private static final int ITYP_DOUBLE = 3; // double - private static final int ITYP_DATETIME = 4; // date/time - private static final int ITYP_BINARY = 5; // binary (byte array) - private static final int ITYP_STRUCT = 6; // struct (Map) - private static final int ITYP_ARRAY = 7; // array (List) - - private static final Map MAP_TYPE; // maps element names to type values - - private static final DateFormat s_iso8601; // used to format and parse date/time values - /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- @@ -120,6 +96,7 @@ class XmlRpcRequest extends BaseDelegatingServiceProvider implements Request } // end catch + XmlRpcSerializer serializer = XmlRpcSerializer.get(); try { // load the XML body of the request, get the method name and parameters Element method_call = loader.getRootElement(request_doc,"methodCall"); @@ -135,7 +112,7 @@ class XmlRpcRequest extends BaseDelegatingServiceProvider implements Request { // get the subelement from each and store it Element elt = (Element)(it.next()); Element value_elt = loader.getSubElement(elt,"value"); - m_params.put(String.valueOf(ndx++),parseValue(value_elt)); + m_params.put(String.valueOf(ndx++),serializer.deserialize(value_elt)); } // end while @@ -155,274 +132,6 @@ class XmlRpcRequest extends BaseDelegatingServiceProvider implements Request } // end constructor - /*-------------------------------------------------------------------------------- - * Internal operations - *-------------------------------------------------------------------------------- - */ - - /** - * Parses an XML-RPC <value/> element. - * - * @param elt The node coresponding to the <value/> element. - * @return The parsed value. - * @exception com.silverwrist.dynamo.xmlrpc.FaultCode If an error occurs in parsing the value. - */ - private static final Object parseValue(Element elt) throws FaultCode - { - NodeList nl = elt.getChildNodes(); - Element type_spec = null; - for (int i=0; i element"); - type_spec = (Element)n; - - } // end for - - Object rc = null; - if (type_spec!=null) - { // figure out what type this element is - Integer tval = (Integer)(MAP_TYPE.get(type_spec.getTagName())); - if (tval==null) - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid type \"" + type_spec.getTagName() - + "\""); - DOMElementHelper h = new DOMElementHelper(type_spec); - switch (tval.intValue()) - { // based on the type, act on the contents - case ITYP_INTEGER: - try - { // parse the integer value - rc = new Integer(Integer.parseInt(h.getElementText(),10)); - - } // end try - catch (NumberFormatException e) - { // value wasn't an integer - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid integer format",e); - - } // end catch - catch (NullPointerException e) - { // value was null - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"integer value not specified"); - - } // end catch - break; - - case ITYP_BOOLEAN: - { // test the Boolean value - String s = h.getElementText(); - if (s==null) - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"boolean value not specified"); - if (s.equals("1")) - rc = Boolean.TRUE; - else if (s.equals("0")) - rc = Boolean.FALSE; - else - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid boolean format"); - - } // end case - break; - - case ITYP_STRING: - { // get the string value and return it - rc = h.getElementText(); - if (rc==null) - rc = EMPTY_STRING; - - } // end case - break; - - case ITYP_DOUBLE: - try - { // convert to a Double value - rc = new Double(h.getElementText()); - - } // end try - catch (NumberFormatException e) - { // value wasn't an integer - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid double format",e); - - } // end catch - catch (NullPointerException e) - { // value was null - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"double value not specified"); - - } // end catch - break; - - case ITYP_DATETIME: - try - { // convert to a Date value - rc = s_iso8601.parse(h.getElementText()); - - } // end try - catch (java.text.ParseException e) - { // date couldn't be parsed - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid ISO 8601 date format",e); - - } // end catch - catch (NullPointerException e) - { // no value in there... - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"date value not specified"); - - } // end catch - break; - - case ITYP_BINARY: - try - { // get the string equivalent of the formatted data - String s = h.getElementText(); - if (s==null) - { // null string - return null array - rc = new byte[0]; - break; - - } // end if - - // get a stream of encoded bytes - ByteArrayInputStream encoded_stm = new ByteArrayInputStream(s.getBytes("US-ASCII")); - - // use the JavaMail MIME decoder to turn it into decoded bytes - InputStream decoded_stm = MimeUtility.decode(encoded_stm,"base64"); - - // copy the decoded bytes to a new array - ByteArrayOutputStream output = new ByteArrayOutputStream(); - IOUtils.copy(decoded_stm,output); - IOUtils.shutdown(decoded_stm); - IOUtils.shutdown(encoded_stm); - - // retrieve the output array - rc = output.toByteArray(); - IOUtils.shutdown(output); - - } // end try - catch (UnsupportedEncodingException e) - { // WTF? shouldn't happen - throw new SystemFaultCode(SystemFaultCode.INTERNAL_ERROR,"internal error: unsupported encoding",e); - - } // end catch - catch (IOException e) - { // some sort of error copying binary values around - throw new SystemFaultCode(SystemFaultCode.INTERNAL_ERROR,"unable to get binary data",e); - - } // end catch - catch (MessagingException e) - { // error in the MIME parsing - dump it out - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid binary data format",e); - - } // end catch - break; - - case ITYP_STRUCT: - rc = parseStruct(type_spec); - break; - - case ITYP_ARRAY: - rc = parseArray(type_spec); - break; - - default: - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid type \"" - + type_spec.getTagName() + "\""); - - } // end switch - - } // end if - else - { // if there's no type-specifying element, treat it as a String - DOMElementHelper h = new DOMElementHelper(elt); - rc = h.getElementText(); - if (rc==null) - rc = EMPTY_STRING; - - } // end else - - return rc; - - } // end parseValue - - /** - * Parses an XML-RPC <struct/> element. - * - * @param elt The node coresponding to the <struct/> element. - * @return The parsed value. - * @exception com.silverwrist.dynamo.xmlrpc.FaultCode If an error occurs in parsing the value. - */ - private static final Map parseStruct(Element elt) throws FaultCode - { - XMLLoader loader = XMLLoader.get(); - HashMap rc = new HashMap(); - try - { // get all sub-elements and process them - List l = loader.getMatchingSubElements(elt,"member"); - Iterator it = l.iterator(); - while (it.hasNext()) - { // get each and add its and to the map - Element x = (Element)(it.next()); - DOMElementHelper h = new DOMElementHelper(x); - String name = loader.getSubElementText(h,"name"); - Element val_elt = loader.getSubElement(h,"value"); - rc.put(name,parseValue(val_elt)); - - } // end while - - } // end try - catch (XMLLoadException e) - { // translate load exception - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,e); - - } // end catch - - if (rc.isEmpty()) - return Collections.EMPTY_MAP; - else - return Collections.unmodifiableMap(rc); - - } // end parseStruct - - /** - * Parses an XML-RPC <array/> element. - * - * @param elt The node coresponding to the <array/> element. - * @return The parsed value. - * @exception com.silverwrist.dynamo.xmlrpc.FaultCode If an error occurs in parsing the value. - */ - private static final List parseArray(Element elt) throws FaultCode - { - XMLLoader loader = XMLLoader.get(); - ArrayList rc = null; - try - { // get the sub-element - Element data_elt = loader.getSubElement(elt,"data"); - - // get the values it contains - List l = loader.getMatchingSubElements(data_elt,"value"); - rc = new ArrayList(l.size()); - Iterator it = l.iterator(); - while (it.hasNext()) - { // parse the elements and add them to the list - Element val_elt = (Element)(it.next()); - rc.add(parseValue(val_elt)); - - } // end while - - } // end try - catch (XMLLoadException e) - { // translate load exception - throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,e); - - } // end catch - - if ((rc==null) || rc.isEmpty()) - return Collections.EMPTY_LIST; - else - return Collections.unmodifiableList(rc); - - } // end parseArray - /*-------------------------------------------------------------------------------- * Implementations from interface ObjectProvider *-------------------------------------------------------------------------------- @@ -670,31 +379,4 @@ class XmlRpcRequest extends BaseDelegatingServiceProvider implements Request } // end getLocales - /*-------------------------------------------------------------------------------- - * Static initializer - *-------------------------------------------------------------------------------- - */ - - static - { // Initialize the type map. - HashMap tmp = new HashMap(); - Integer foo = new Integer(ITYP_INTEGER); - tmp.put("i4",foo); - tmp.put("int",foo); - tmp.put("boolean",new Integer(ITYP_BOOLEAN)); - tmp.put("string",new Integer(ITYP_STRING)); - tmp.put("double",new Integer(ITYP_DOUBLE)); - tmp.put("dateTime.iso8601",new Integer(ITYP_DATETIME)); - tmp.put("base64",new Integer(ITYP_BINARY)); - tmp.put("struct",new Integer(ITYP_STRUCT)); - tmp.put("array",new Integer(ITYP_ARRAY)); - MAP_TYPE = Collections.unmodifiableMap(tmp); - - // Initialize the ISO 8601 date formatter. - SimpleDateFormat iso = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); - iso.setCalendar(new GregorianCalendar(new SimpleTimeZone(0,"UTC"))); - s_iso8601 = iso; - - } // end static initializer - } // end class XmlRpcRequest diff --git a/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcResult.java b/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcResult.java index eaa8aec..bcf6dd0 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcResult.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/xmlrpc/XmlRpcResult.java @@ -11,40 +11,19 @@ * * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.dynamo.xmlrpc; import java.io.*; -import java.lang.ref.Reference; -import java.sql.Blob; -import java.sql.SQLException; -import java.text.*; -import java.util.*; -import javax.mail.*; -import javax.mail.internet.*; -import com.silverwrist.util.*; import com.silverwrist.dynamo.HttpStatusCode; import com.silverwrist.dynamo.except.*; import com.silverwrist.dynamo.iface.*; public class XmlRpcResult implements SelfRenderable, XmlRpcSelfSerializing { - /*-------------------------------------------------------------------------------- - * Static data members - *-------------------------------------------------------------------------------- - */ - - private static final String SERIALIZED_NULL = "null"; - private static final String START_ARRAY = "\r\n"; - private static final String END_ARRAY = ""; - private static final String START_VALUE = ""; - private static final String END_VALUE = "\r\n"; - - private static final DateFormat s_iso8601; - /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- @@ -73,7 +52,7 @@ public class XmlRpcResult implements SelfRenderable, XmlRpcSelfSerializing // Serialize the output value to a StringWriter. StringWriter wr = new StringWriter(); wr.write("\r\n"); - serialize(wr,m_obj); + XmlRpcSerializer.get().serialize(wr,m_obj); wr.write("\r\n\r\n"); // Now render the binary equivalent of this structure. @@ -107,513 +86,8 @@ public class XmlRpcResult implements SelfRenderable, XmlRpcSelfSerializing public void serializeXmlRpc(Writer wr) throws IOException { - serialize(wr,m_obj); + XmlRpcSerializer.get().serialize(wr,m_obj); } // end serializeXmlRpc - /*-------------------------------------------------------------------------------- - * External static operations - *-------------------------------------------------------------------------------- - */ - - public static void serializeBinary(Writer wr, InputStream stm) throws IOException - { - try - { // Encode the data as BASE-64. - ByteArrayOutputStream internal_stm = new ByteArrayOutputStream(); - OutputStream encode_stm = MimeUtility.encode(internal_stm,"base64"); - IOUtils.copy(stm,encode_stm); - encode_stm.flush(); - - // turn our encoded output into an InputStream - ByteArrayInputStream internal2_stm = new ByteArrayInputStream(internal_stm.toByteArray()); - IOUtils.shutdown(encode_stm); - IOUtils.shutdown(internal_stm); - - // write out the data - InputStreamReader rd = new InputStreamReader(internal2_stm,"US-ASCII"); - wr.write(""); - IOUtils.copy(rd,wr); - wr.write(""); - IOUtils.shutdown(rd); - IOUtils.shutdown(internal2_stm); - - } // end try - catch (MessagingException e) - { // encoder might have an error - throw new IOException("error encoding binary data"); - - } // end catch - - } // end serializeBinary - - public static void serialize(Writer wr, boolean b) throws IOException - { - wr.write(""); - wr.write(b ? "1" : "0"); - wr.write(""); - - } // end serialize - - public static void serialize(Writer wr, boolean[] b) throws IOException - { - wr.write(START_ARRAY); - for (int i=0; i"); - wr.write(c); - wr.write(""); - - } // end serialize - - public static void serialize(Writer wr, char[] c) throws IOException - { - wr.write(START_ARRAY); - for (int i=0; i"); - wr.write(String.valueOf(i)); - wr.write(""); - - } // end serialize - - public static void serialize(Writer wr, int[] ia) throws IOException - { - wr.write(START_ARRAY); - for (int i=0; i"); - wr.write(String.valueOf(d)); - wr.write(""); - - } // end serialize - - public static void serialize(Writer wr, double[] d) throws IOException - { - wr.write(START_ARRAY); - for (int i=0; i"); - - // Encode the year first. - StringBuffer conv = new StringBuffer("0000"); - conv.append(cal.get(Calendar.YEAR)); - String c = conv.toString(); - wr.write(c.substring(c.length()-4)); - - // Now the month... - conv.setLength(0); - conv.append("00").append(cal.get(Calendar.MONTH) - Calendar.JANUARY + 1); - c = conv.toString(); - wr.write(c.substring(c.length()-2)); - - // And the day... - conv.setLength(0); - conv.append("00").append(cal.get(Calendar.DAY_OF_MONTH)); - c = conv.toString(); - wr.write(c.substring(c.length()-2)); - wr.write("T"); - - // And the hour... - conv.setLength(0); - conv.append("00").append(cal.get(Calendar.HOUR_OF_DAY)); - c = conv.toString(); - wr.write(c.substring(c.length()-2)); - wr.write(":"); - - // And the minute... - conv.setLength(0); - conv.append("00").append(cal.get(Calendar.MINUTE)); - c = conv.toString(); - wr.write(c.substring(c.length()-2)); - wr.write(":"); - - // And the second... - conv.setLength(0); - conv.append("00").append(cal.get(Calendar.SECOND)); - c = conv.toString(); - wr.write(c.substring(c.length()-2)); - - // And we're done! - wr.write(""); - - } // end serializeCalendar - - public static void serializeCollection(Writer wr, Collection coll) throws IOException - { - serializeIterator(wr,coll.iterator()); - - } // end serializeCollection - - public static void serializeDate(Writer wr, Date d) throws IOException - { - wr.write(""); - wr.write(s_iso8601.format(d)); - wr.write(""); - - } // end serializeDate - - public static void serializeEnumeration(Writer wr, Enumeration enum) throws IOException - { - wr.write(START_ARRAY); - while (enum.hasMoreElements()) - { // serialize the values we contain - wr.write(START_VALUE); - serialize(wr,enum.nextElement()); - wr.write(END_VALUE); - - } // end for - - wr.write(END_ARRAY); - - } // end serializeIterator - - public static void serializeIterator(Writer wr, Iterator it) throws IOException - { - wr.write(START_ARRAY); - while (it.hasNext()) - { // serialize the values we contain - wr.write(START_VALUE); - serialize(wr,it.next()); - wr.write(END_VALUE); - - } // end for - - wr.write(END_ARRAY); - - } // end serializeIterator - - public static void serializeMap(Writer wr, Map map) throws IOException - { - wr.write("\r\n"); - Iterator it = map.entrySet().iterator(); - while (it.hasNext()) - { // write each entry in turn - Map.Entry ntry = (Map.Entry)(it.next()); - wr.write("\r\n"); - wr.write(StringUtils.encodeHTML(ntry.getKey().toString())); - wr.write("\r\n"); - serialize(wr,ntry.getValue()); - wr.write("\r\n"); - - } // end while - - wr.write("\r\n"); - - } // end serializeMap - - public static void serializeString(Writer wr, String s) throws IOException - { - wr.write(""); - wr.write(StringUtils.encodeHTML(s)); - wr.write(""); - - } // end serializeString - - public static void serialize(Writer wr, Object obj) throws IOException - { - if (obj==null) - { // null object - bye! - wr.write(SERIALIZED_NULL); - return; - - } // end if - - if (obj.getClass().isArray()) - { // for arrays, serialize them specially - Class component = obj.getClass().getComponentType(); - if (component==Boolean.TYPE) - serialize(wr,(boolean[])obj); - else if (component==Byte.TYPE) - serialize(wr,(byte[])obj); - else if (component==Character.TYPE) - serialize(wr,(char[])obj); - else if (component==Short.TYPE) - serialize(wr,(short[])obj); - else if (component==Integer.TYPE) - serialize(wr,(int[])obj); - else if (component==Long.TYPE) - serialize(wr,(long[])obj); - else if (component==Float.TYPE) - serialize(wr,(float[])obj); - else if (component==Double.TYPE) - serialize(wr,(double[])obj); - else - serialize(wr,(Object[])obj); - return; - - } // end if - - if (obj instanceof XmlRpcSelfSerializing) - { // some objects may be self-serializing - ((XmlRpcSelfSerializing)obj).serializeXmlRpc(wr); - return; - - } // end if - - if (obj instanceof DynamicWrapper) - { // for dynamic wrappers, unwrap them - serialize(wr,((DynamicWrapper)obj).unwrap()); - return; - - } // end if - - if (obj instanceof InputStream) - { // for InputStream, turn it into binary - serializeBinary(wr,(InputStream)obj); - return; - - } // end if - - if (obj instanceof Blob) - { // serialize the blob as a binary stream - try - { // just get its input stream - serializeBinary(wr,((Blob)obj).getBinaryStream()); - return; - - } // end try - catch (SQLException e) - { // fault on error here - throw new IOException("error writing binary data"); - - } // end catch - - } // end if - - if (obj instanceof Calendar) - { // serialize a Calendar - serializeCalendar(wr,(Calendar)obj); - return; - - } // end if - - if (obj instanceof Collection) - { // serialize a Collection - serializeCollection(wr,(Collection)obj); - return; - - } // end if - - if (obj instanceof Date) - { // serialize a Date - serializeDate(wr,(Date)obj); - return; - - } // end if - - if (obj instanceof Enumeration) - { // serialize a Enumeration - serializeEnumeration(wr,(Enumeration)obj); - return; - - } // end if - - if (obj instanceof Iterator) - { // serialize a Iterator - serializeIterator(wr,(Iterator)obj); - return; - - } // end if - - if (obj instanceof Map) - { // serialize a Map - serializeMap(wr,(Map)obj); - return; - - } // end if - - if (obj instanceof Reference) - { // for a Reference, serialize the referent - serialize(wr,((Reference)obj).get()); - return; - - } // end if - - if (obj instanceof Boolean) - { // serialize the boolean value - serialize(wr,((Boolean)obj).booleanValue()); - return; - - } // end if - - if (obj instanceof Character) - { // serialize the character value - serialize(wr,((Character)obj).charValue()); - return; - - } // end if - - if ((obj instanceof Byte) || (obj instanceof Short) || (obj instanceof Integer)) - { // serialize the integer value - serialize(wr,((Number)obj).intValue()); - return; - - } // end if - - if ((obj instanceof Long) || (obj instanceof Float) || (obj instanceof Double)) - { // serialize the double value - serialize(wr,((Number)obj).doubleValue()); - return; - - } // end if - - // String and StringBuffer are handled properly by the fallback mechanism below - - // last-ditch fallback method - use toString, get the string, and encode it - serializeString(wr,obj.toString()); - - } // end serialize - - public static void serialize(Writer wr, Object[] arr) throws IOException - { - wr.write(START_ARRAY); - for (int i=0; i. + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.dynamo.xmlrpc; + +import java.io.*; +import java.lang.ref.Reference; +import java.sql.Blob; +import java.sql.SQLException; +import java.text.*; +import java.util.*; +import javax.mail.*; +import javax.mail.internet.*; +import org.w3c.dom.*; +import com.silverwrist.util.*; +import com.silverwrist.util.xml.*; +import com.silverwrist.dynamo.except.*; +import com.silverwrist.dynamo.iface.*; + +public class XmlRpcSerializer +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static XmlRpcSerializer _self = null; + + private static final String SERIALIZED_NULL = "null"; + private static final String START_ARRAY = "\r\n"; + private static final String END_ARRAY = ""; + private static final String START_VALUE = ""; + private static final String END_VALUE = "\r\n"; + + // Indicates the types of values associated with the request. + private static final int ITYP_INTEGER = 0; // integer + private static final int ITYP_BOOLEAN = 1; // Boolean + private static final int ITYP_STRING = 2; // string + private static final int ITYP_DOUBLE = 3; // double + private static final int ITYP_DATETIME = 4; // date/time + private static final int ITYP_BINARY = 5; // binary (byte array) + private static final int ITYP_STRUCT = 6; // struct (Map) + private static final int ITYP_ARRAY = 7; // array (List) + + private static final String EMPTY_STRING = ""; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private final DateFormat m_iso8601; // the ISO 8601 date format + private final Map m_type_map; // mapping of type values to indices + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + private XmlRpcSerializer() + { + // Initialize the ISO 8601 date formatter. + SimpleDateFormat iso = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + iso.setCalendar(new GregorianCalendar(new SimpleTimeZone(0,"UTC"))); + m_iso8601 = iso; + + // Initialize the type map. + HashMap tmp = new HashMap(); + Integer foo = new Integer(ITYP_INTEGER); + tmp.put("i4",foo); + tmp.put("int",foo); + tmp.put("boolean",new Integer(ITYP_BOOLEAN)); + tmp.put("string",new Integer(ITYP_STRING)); + tmp.put("double",new Integer(ITYP_DOUBLE)); + tmp.put("dateTime.iso8601",new Integer(ITYP_DATETIME)); + tmp.put("base64",new Integer(ITYP_BINARY)); + tmp.put("struct",new Integer(ITYP_STRUCT)); + tmp.put("array",new Integer(ITYP_ARRAY)); + m_type_map = Collections.unmodifiableMap(tmp); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + /** + * Deserializes an XML-RPC <struct/> element. + * + * @param elt The node coresponding to the <struct/> element. + * @return The parsed value. + * @exception com.silverwrist.dynamo.xmlrpc.FaultCode If an error occurs in parsing the value. + */ + private final Map deserializeStruct(Element elt) throws FaultCode + { + XMLLoader loader = XMLLoader.get(); + HashMap rc = new HashMap(); + try + { // get all sub-elements and process them + List l = loader.getMatchingSubElements(elt,"member"); + Iterator it = l.iterator(); + while (it.hasNext()) + { // get each and add its and to the map + Element x = (Element)(it.next()); + DOMElementHelper h = new DOMElementHelper(x); + String name = loader.getSubElementText(h,"name"); + Element val_elt = loader.getSubElement(h,"value"); + rc.put(name,deserialize(val_elt)); + + } // end while + + } // end try + catch (XMLLoadException e) + { // translate load exception + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,e); + + } // end catch + + if (rc.isEmpty()) + return Collections.EMPTY_MAP; + else + return Collections.unmodifiableMap(rc); + + } // end deserializeStruct + + /** + * Deserializes an XML-RPC <array/> element. + * + * @param elt The node coresponding to the <array/> element. + * @return The parsed value. + * @exception com.silverwrist.dynamo.xmlrpc.FaultCode If an error occurs in parsing the value. + */ + private final List deserializeArray(Element elt) throws FaultCode + { + XMLLoader loader = XMLLoader.get(); + ArrayList rc = null; + try + { // get the sub-element + Element data_elt = loader.getSubElement(elt,"data"); + + // get the values it contains + List l = loader.getMatchingSubElements(data_elt,"value"); + rc = new ArrayList(l.size()); + Iterator it = l.iterator(); + while (it.hasNext()) + { // parse the elements and add them to the list + Element val_elt = (Element)(it.next()); + rc.add(deserialize(val_elt)); + + } // end while + + } // end try + catch (XMLLoadException e) + { // translate load exception + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,e); + + } // end catch + + if ((rc==null) || rc.isEmpty()) + return Collections.EMPTY_LIST; + else + return Collections.unmodifiableList(rc); + + } // end deserializeArray + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public void serializeBinary(Writer wr, InputStream stm) throws IOException + { + try + { // Encode the data as BASE-64. + ByteArrayOutputStream internal_stm = new ByteArrayOutputStream(); + OutputStream encode_stm = MimeUtility.encode(internal_stm,"base64"); + IOUtils.copy(stm,encode_stm); + encode_stm.flush(); + + // turn our encoded output into an InputStream + ByteArrayInputStream internal2_stm = new ByteArrayInputStream(internal_stm.toByteArray()); + IOUtils.shutdown(encode_stm); + IOUtils.shutdown(internal_stm); + + // write out the data + InputStreamReader rd = new InputStreamReader(internal2_stm,"US-ASCII"); + wr.write(""); + IOUtils.copy(rd,wr); + wr.write(""); + IOUtils.shutdown(rd); + IOUtils.shutdown(internal2_stm); + + } // end try + catch (MessagingException e) + { // encoder might have an error + throw new IOException("error encoding binary data"); + + } // end catch + + } // end serializeBinary + + public void serialize(Writer wr, boolean b) throws IOException + { + wr.write(""); + wr.write(b ? "1" : "0"); + wr.write(""); + + } // end serialize + + public void serialize(Writer wr, boolean[] b) throws IOException + { + wr.write(START_ARRAY); + for (int i=0; i"); + wr.write(String.valueOf(i)); + wr.write(""); + + } // end serialize + + public void serialize(Writer wr, int[] ia) throws IOException + { + wr.write(START_ARRAY); + for (int i=0; i"); + wr.write(String.valueOf(d)); + wr.write(""); + + } // end serialize + + public void serialize(Writer wr, double[] d) throws IOException + { + wr.write(START_ARRAY); + for (int i=0; i"); + + // Encode the year first. + StringBuffer conv = new StringBuffer("0000"); + conv.append(cal.get(Calendar.YEAR)); + String c = conv.toString(); + wr.write(c.substring(c.length()-4)); + + // Now the month... + conv.setLength(0); + conv.append("00").append(cal.get(Calendar.MONTH) - Calendar.JANUARY + 1); + c = conv.toString(); + wr.write(c.substring(c.length()-2)); + + // And the day... + conv.setLength(0); + conv.append("00").append(cal.get(Calendar.DAY_OF_MONTH)); + c = conv.toString(); + wr.write(c.substring(c.length()-2)); + wr.write("T"); + + // And the hour... + conv.setLength(0); + conv.append("00").append(cal.get(Calendar.HOUR_OF_DAY)); + c = conv.toString(); + wr.write(c.substring(c.length()-2)); + wr.write(":"); + + // And the minute... + conv.setLength(0); + conv.append("00").append(cal.get(Calendar.MINUTE)); + c = conv.toString(); + wr.write(c.substring(c.length()-2)); + wr.write(":"); + + // And the second... + conv.setLength(0); + conv.append("00").append(cal.get(Calendar.SECOND)); + c = conv.toString(); + wr.write(c.substring(c.length()-2)); + + // And we're done! + wr.write(""); + + } // end serializeCalendar + + public void serializeCollection(Writer wr, Collection coll) throws IOException + { + serializeIterator(wr,coll.iterator()); + + } // end serializeCollection + + public void serializeDate(Writer wr, Date d) throws IOException + { + wr.write(""); + wr.write(m_iso8601.format(d)); + wr.write(""); + + } // end serializeDate + + public void serializeEnumeration(Writer wr, Enumeration enum) throws IOException + { + wr.write(START_ARRAY); + while (enum.hasMoreElements()) + { // serialize the values we contain + wr.write(START_VALUE); + serialize(wr,enum.nextElement()); + wr.write(END_VALUE); + + } // end for + + wr.write(END_ARRAY); + + } // end serializeIterator + + public void serializeIterator(Writer wr, Iterator it) throws IOException + { + wr.write(START_ARRAY); + while (it.hasNext()) + { // serialize the values we contain + wr.write(START_VALUE); + serialize(wr,it.next()); + wr.write(END_VALUE); + + } // end for + + wr.write(END_ARRAY); + + } // end serializeIterator + + public void serializeMap(Writer wr, Map map) throws IOException + { + wr.write("\r\n"); + Iterator it = map.entrySet().iterator(); + while (it.hasNext()) + { // write each entry in turn + Map.Entry ntry = (Map.Entry)(it.next()); + wr.write("\r\n"); + wr.write(StringUtils.encodeHTML(ntry.getKey().toString())); + wr.write("\r\n"); + serialize(wr,ntry.getValue()); + wr.write("\r\n"); + + } // end while + + wr.write("\r\n"); + + } // end serializeMap + + public void serializeString(Writer wr, String s) throws IOException + { + wr.write(""); + wr.write(StringUtils.encodeHTML(s)); + wr.write(""); + + } // end serializeString + + public void serialize(Writer wr, Object obj) throws IOException + { + if (obj==null) + { // null object - bye! + wr.write(SERIALIZED_NULL); + return; + + } // end if + + if (obj.getClass().isArray()) + { // for arrays, serialize them specially + Class component = obj.getClass().getComponentType(); + if (component==Boolean.TYPE) + serialize(wr,(boolean[])obj); + else if (component==Byte.TYPE) + serialize(wr,(byte[])obj); + else if (component==Character.TYPE) + serialize(wr,(char[])obj); + else if (component==Short.TYPE) + serialize(wr,(short[])obj); + else if (component==Integer.TYPE) + serialize(wr,(int[])obj); + else if (component==Long.TYPE) + serialize(wr,(long[])obj); + else if (component==Float.TYPE) + serialize(wr,(float[])obj); + else if (component==Double.TYPE) + serialize(wr,(double[])obj); + else + serialize(wr,(Object[])obj); + return; + + } // end if + + if (obj instanceof XmlRpcSelfSerializing) + { // some objects may be self-serializing + ((XmlRpcSelfSerializing)obj).serializeXmlRpc(wr); + return; + + } // end if + + if (obj instanceof DynamicWrapper) + { // for dynamic wrappers, unwrap them + serialize(wr,((DynamicWrapper)obj).unwrap()); + return; + + } // end if + + if (obj instanceof InputStream) + { // for InputStream, turn it into binary + serializeBinary(wr,(InputStream)obj); + return; + + } // end if + + if (obj instanceof Blob) + { // serialize the blob as a binary stream + try + { // just get its input stream + serializeBinary(wr,((Blob)obj).getBinaryStream()); + return; + + } // end try + catch (SQLException e) + { // fault on error here + throw new IOException("error writing binary data"); + + } // end catch + + } // end if + + if (obj instanceof Calendar) + { // serialize a Calendar + serializeCalendar(wr,(Calendar)obj); + return; + + } // end if + + if (obj instanceof Collection) + { // serialize a Collection + serializeCollection(wr,(Collection)obj); + return; + + } // end if + + if (obj instanceof Date) + { // serialize a Date + serializeDate(wr,(Date)obj); + return; + + } // end if + + if (obj instanceof Enumeration) + { // serialize a Enumeration + serializeEnumeration(wr,(Enumeration)obj); + return; + + } // end if + + if (obj instanceof Iterator) + { // serialize a Iterator + serializeIterator(wr,(Iterator)obj); + return; + + } // end if + + if (obj instanceof Map) + { // serialize a Map + serializeMap(wr,(Map)obj); + return; + + } // end if + + if (obj instanceof Reference) + { // for a Reference, serialize the referent + serialize(wr,((Reference)obj).get()); + return; + + } // end if + + if (obj instanceof Boolean) + { // serialize the boolean value + serialize(wr,((Boolean)obj).booleanValue()); + return; + + } // end if + + if (obj instanceof Character) + { // serialize the character value + serialize(wr,((Character)obj).charValue()); + return; + + } // end if + + if ((obj instanceof Byte) || (obj instanceof Short) || (obj instanceof Integer)) + { // serialize the integer value + serialize(wr,((Number)obj).intValue()); + return; + + } // end if + + if ((obj instanceof Long) || (obj instanceof Float) || (obj instanceof Double)) + { // serialize the double value + serialize(wr,((Number)obj).doubleValue()); + return; + + } // end if + + // String and StringBuffer are handled properly by the fallback mechanism below + + // last-ditch fallback method - use toString, get the string, and encode it + serializeString(wr,obj.toString()); + + } // end serialize + + public void serialize(Writer wr, Object[] arr) throws IOException + { + wr.write(START_ARRAY); + for (int i=0; i element"); + type_spec = (Element)n; + + } // end for + + Object rc = null; + if (type_spec!=null) + { // figure out what type this element is + Integer tval = (Integer)(m_type_map.get(type_spec.getTagName())); + if (tval==null) + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid type \"" + type_spec.getTagName() + "\""); + DOMElementHelper h = new DOMElementHelper(type_spec); + switch (tval.intValue()) + { // based on the type, act on the contents + case ITYP_INTEGER: + try + { // parse the integer value + rc = new Integer(Integer.parseInt(h.getElementText(),10)); + + } // end try + catch (NumberFormatException e) + { // value wasn't an integer + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid integer format",e); + + } // end catch + catch (NullPointerException e) + { // value was null + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"integer value not specified"); + + } // end catch + break; + + case ITYP_BOOLEAN: + { // test the Boolean value + String s = h.getElementText(); + if (s==null) + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"boolean value not specified"); + if (s.equals("1")) + rc = Boolean.TRUE; + else if (s.equals("0")) + rc = Boolean.FALSE; + else + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid boolean format"); + + } // end case + break; + + case ITYP_STRING: + { // get the string value and return it + rc = h.getElementText(); + if (rc==null) + rc = EMPTY_STRING; + + } // end case + break; + + case ITYP_DOUBLE: + try + { // convert to a Double value + rc = new Double(h.getElementText()); + + } // end try + catch (NumberFormatException e) + { // value wasn't an integer + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid double format",e); + + } // end catch + catch (NullPointerException e) + { // value was null + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"double value not specified"); + + } // end catch + break; + + case ITYP_DATETIME: + try + { // convert to a Date value + rc = m_iso8601.parse(h.getElementText()); + + } // end try + catch (java.text.ParseException e) + { // date couldn't be parsed + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid ISO 8601 date format",e); + + } // end catch + catch (NullPointerException e) + { // no value in there... + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"date value not specified"); + + } // end catch + break; + + case ITYP_BINARY: + try + { // get the string equivalent of the formatted data + String s = h.getElementText(); + if (s==null) + { // null string - return null array + rc = new byte[0]; + break; + + } // end if + + // get a stream of encoded bytes + ByteArrayInputStream encoded_stm = new ByteArrayInputStream(s.getBytes("US-ASCII")); + + // use the JavaMail MIME decoder to turn it into decoded bytes + InputStream decoded_stm = MimeUtility.decode(encoded_stm,"base64"); + + // copy the decoded bytes to a new array + ByteArrayOutputStream output = new ByteArrayOutputStream(); + IOUtils.copy(decoded_stm,output); + IOUtils.shutdown(decoded_stm); + IOUtils.shutdown(encoded_stm); + + // retrieve the output array + rc = output.toByteArray(); + IOUtils.shutdown(output); + + } // end try + catch (UnsupportedEncodingException e) + { // WTF? shouldn't happen + throw new SystemFaultCode(SystemFaultCode.INTERNAL_ERROR,"internal error: unsupported encoding",e); + + } // end catch + catch (IOException e) + { // some sort of error copying binary values around + throw new SystemFaultCode(SystemFaultCode.INTERNAL_ERROR,"unable to get binary data",e); + + } // end catch + catch (MessagingException e) + { // error in the MIME parsing - dump it out + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid binary data format",e); + + } // end catch + break; + + case ITYP_STRUCT: + rc = deserializeStruct(type_spec); + break; + + case ITYP_ARRAY: + rc = deserializeArray(type_spec); + break; + + default: + throw new SystemFaultCode(SystemFaultCode.INVALID_REQUEST,"invalid type \"" + + type_spec.getTagName() + "\""); + + } // end switch + + } // end if + else + { // if there's no type-specifying element, treat it as a String + DOMElementHelper h = new DOMElementHelper(elt); + rc = h.getElementText(); + if (rc==null) + rc = EMPTY_STRING; + + } // end else + + return rc; + + } // end deserialize + + /*-------------------------------------------------------------------------------- + * External static operations + *-------------------------------------------------------------------------------- + */ + + public static XmlRpcSerializer get() + { + if (_self==null) + _self = new XmlRpcSerializer(); + return _self; + + } // end get + +} // end class XmlRpcSerializer