Eric J. Bowersox fddeff906d included a script for handling the XML-RPC Validator (UserLand Software),
and debugged the handling of parameter serialization so that I think it
can actually pass now...
2002-01-17 02:52:22 +00:00

467 lines
14 KiB
Java

/*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
*
* 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 <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.ui.rpc;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.util.XMLLoader;
public class XmlRpcRequest
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static Category logger = Category.getInstance(XmlRpcRequest.class);
private static SimpleTimeZone utc = new SimpleTimeZone(0,"UTC");
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
String method_name; // the method name
List method_params; // the method parameters
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
XmlRpcRequest(Document req_doc) throws XmlRpcFault
{
if (req_doc==null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"no XML call structure found");
try
{
// load the initial structure and method name
XMLLoader loader = XMLLoader.get();
Element root = loader.postGetRootElement(req_doc,"methodCall");
DOMElementHelper root_h = new DOMElementHelper(root);
method_name = loader.postGetSubElementText(root_h,"methodName");
// parse the parameters
Element params = root_h.getSubElement("params");
ArrayList tmp_method_params = new ArrayList();
if (params!=null)
{ // look for <param/> children of the <params/> node
NodeList nl = params.getChildNodes();
for (int i=0; i<nl.getLength(); i++)
{ // look at the child node...
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // make sure it's a <param/> node
if (!(n.getNodeName().equals("param")))
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"non-param element found inside \"params\" element");
tmp_method_params.add(parseValue(loader.postGetSubSection((Element)n,"value")));
} // end if
// else ignore this node
} // end for
} // end if
// save the method parameters
if (tmp_method_params.isEmpty())
method_params = Collections.EMPTY_LIST;
else
{ // make it read-only before
tmp_method_params.trimToSize();
method_params = Collections.unmodifiableList(tmp_method_params);
} // end else
} // end try
catch (ValidationException ve)
{ // validation exceptions get translated to XML-RPC faults
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,ve);
} // end catch
} // end constructor
/*--------------------------------------------------------------------------------
* Internal static operations
*--------------------------------------------------------------------------------
*/
private static final Object parseValue(Element val) throws ValidationException, XmlRpcFault
{
XMLLoader loader = XMLLoader.get();
// see if the value has an embedded type...
NodeList nl = val.getChildNodes();
Element type = null;
for (int i=0; i<nl.getLength(); i++)
{ // look for any sub-element within a value
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // save off the type-specific element
if (type!=null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"more than one type-specific element within a \"value\" element");
type = (Element)n;
} // end if
} // end for
if (type!=null)
{ // look at the contents of the "type" element
DOMElementHelper h = new DOMElementHelper(type);
final String name = type.getTagName();
if (name.equals("i4") || name.equals("int"))
{ // parse an integer value
String s = h.getElementText();
if (s==null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid null inside \"" + name + "\" value");
Integer rc = null;
try
{ // create the return value
rc = new Integer(s.trim());
} // end try
catch (NumberFormatException nfe)
{ // bogus number!
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid integer value inside \"" + name
+ "\" value");
} // end catch
return rc;
} // end if
else if (name.equals("boolean"))
{ // parse a boolean value
String s = h.getElementText();
if (s==null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid null inside \"boolean\" value");
s = s.trim();
if (s.equals("1"))
return Boolean.TRUE;
else if (s.equals("0"))
return Boolean.FALSE;
else
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid value inside \"boolean\" value");
} // end else if
else if (name.equals("string"))
{ // parse a string value
String s = h.getElementText();
if (s==null)
s = "";
return s;
} // end else if
else if (name.equals("double"))
{ // parse a double-precision floating-point value
String s = h.getElementText();
if (s==null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid null inside \"double\" value");
Double rc = null;
try
{ // create the return value
rc = new Double(s.trim());
} // end try
catch (NumberFormatException nfe)
{ // bogus number!
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"invalid floating-point value inside \"double\" value");
} // end catch
return rc;
} // end else if
else if (name.equals("dateTime.iso8601"))
{ // parse a date/time value
String dstr = h.getElementText();
if (dstr==null)
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid null inside \"dateTime.iso8601\" value");
dstr = dstr.trim();
// run some validation checks on the string
if ((dstr.charAt(8)!='T') || (dstr.charAt(11)!=':') || (dstr.charAt(14)!=':'))
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"invalid ISO8601 date/time value inside \"dateTime.iso8601\" value");
try
{ // use a GregorianCalendar to convert the fields into the appropriate format
GregorianCalendar cal = new GregorianCalendar(utc);
cal.set(Calendar.YEAR,Integer.parseInt(dstr.substring(0,4)));
cal.set(Calendar.MONTH,Integer.parseInt(dstr.substring(4,6)) - 1 + Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH,Integer.parseInt(dstr.substring(6,8)));
cal.set(Calendar.HOUR_OF_DAY,Integer.parseInt(dstr.substring(9,11)));
cal.set(Calendar.MINUTE,Integer.parseInt(dstr.substring(12,14)));
cal.set(Calendar.SECOND,Integer.parseInt(dstr.substring(15,17)));
return cal.getTime();
} // end try
catch (NumberFormatException nfe)
{ // numeric conversion error!
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"invalid ISO8601 date/time value inside \"dateTime.iso8601\" value");
} // end catch
} // end else if
else if (name.equals("base64"))
{ // parse a binary value
String s = h.getElementText();
if (s==null)
return new byte[0];
byte[] encoded_data = null;
try
{ // get the encoded data
encoded_data = s.getBytes("US-ASCII");
} // end try
catch (UnsupportedEncodingException uee)
{ // this is bogus
throw new InternalStateError("US-ASCII reports as 'unsupported'...shouldn't happen");
} // end catch
byte[] rc = null;
try
{ // wrap the byte array in a stream and decode it
// note: we use JavaMail's MIME decoder here
ByteArrayInputStream encoded_stm = new ByteArrayInputStream(encoded_data);
InputStream decoded_stm = MimeUtility.decode(encoded_stm,"base64");
// copy the decoded information to a new stream
ByteArrayOutputStream out_stm = new ByteArrayOutputStream();
IOUtil.copy(decoded_stm,out_stm);
IOUtil.shutdown(decoded_stm);
IOUtil.shutdown(encoded_stm);
// save the byte array backing the new stream
rc = out_stm.toByteArray();
IOUtil.shutdown(out_stm);
} // end try
catch (IOException ioe)
{ // error schlepping all those bits around
throw new XmlRpcFault(XmlRpcFault.INTERNAL_ERROR,
"unable to load binary data from \"base64\" value");
} // end catch
catch (MessagingException me)
{ // invalid format
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,"invalid encoded data inside \"base64\" value");
} // end catch
return rc;
} // end else if
else if (name.equals("struct"))
return parseStruct(type);
else if (name.equals("array"))
return parseArray(type);
else
{ // no dice here
logger.error("inside a <value/> element: expected a valid type but got a <" + name + "/>");
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"invalid type element \"" + name + "\" specified inside a \"value\"");
} // end else
} // end if
else
{ // if there's no type-specific element, treat it as a string
DOMElementHelper h = new DOMElementHelper(val);
String s = h.getElementText();
if (s==null)
s = "";
return s;
} // end else
} // end parseValue
private static final List parseArray(Element val) throws XmlRpcFault, ValidationException
{
XMLLoader loader = XMLLoader.get();
Element data = loader.postGetSubSection(val,"data");
NodeList nl = data.getChildNodes();
ArrayList rc = new ArrayList();
for (int i=0; i<nl.getLength(); i++)
{ // look for <value/> elements within the <data/> element
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // make sure we've got a value element here!
if (!(n.getNodeName().equals("value")))
{ // this is a bogus value!
logger.error("inside array <data/>: expected a <value/> but found a <" + n.getNodeName() + "/>");
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"non-value element found inside array \"data\" element");
} // end if
rc.add(parseValue((Element)n));
} // end if
} // end for
if (rc.isEmpty())
return Collections.EMPTY_LIST;
else
{ // trim the result array and return the list
rc.trimToSize();
return Collections.unmodifiableList(rc);
} // end else
} // end parseArray
private static final Map parseStruct(Element val) throws XmlRpcFault, ValidationException
{
XMLLoader loader = XMLLoader.get();
NodeList nl = val.getChildNodes();
HashMap rc = new HashMap();
for (int i=0; i<nl.getLength(); i++)
{ // seek out all the <member/> elements within a <struct/> element
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // make sure we've got a value element here!
if (!(n.getNodeName().equals("member")))
{ // this is a bogus value
logger.error("inside <struct/>: expected a <member/> but found a <" + n.getNodeName() + "/>");
throw new XmlRpcFault(XmlRpcFault.INVALID_REQUEST,
"non-member element found inside \"struct\" element");
} // end if
DOMElementHelper h = new DOMElementHelper((Element)n);
String my_name = loader.postGetSubElementText(h,"name").trim();
Element my_val = loader.postGetSubSection(h,"value");
rc.put(my_name,parseValue(my_val));
} // end if
} // end for
if (rc.isEmpty())
return Collections.EMPTY_MAP;
else
return Collections.unmodifiableMap(rc);
} // end parseStruct
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public final String getMethod()
{
return method_name;
} // end getMethod
public final int getParamCount()
{
return method_params.size();
} // end getParamCount
public final List getParams()
{
return method_params;
} // end getParams
public final Object getParam(int ndx)
{
return method_params.get(ndx);
} // end getParam
public final String getParamType(int ndx)
{
Object foo = method_params.get(ndx);
if (foo instanceof Integer)
return "int";
if (foo instanceof Boolean)
return "boolean";
if (foo instanceof String)
return "string";
if (foo instanceof Double)
return "double";
if (foo instanceof Date)
return "dateTime";
if (foo instanceof byte[])
return "base64";
if (foo instanceof Map)
return "struct";
if (foo instanceof List)
return "array";
return "(unknown)";
} // end getParamType
public final int getParamInt(int ndx) throws XmlRpcFault
{
Object foo = method_params.get(ndx);
if (foo instanceof Integer)
return ((Integer)foo).intValue();
else if (foo instanceof Boolean)
return ((Boolean)foo).booleanValue() ? 1 : 0;
else
throw new XmlRpcFault(XmlRpcFault.INVALID_PARAMS,"parameter type mismatch");
} // end getParamInt
public final double getParamDouble(int ndx) throws XmlRpcFault
{
Object foo = method_params.get(ndx);
if ((foo instanceof Integer) || (foo instanceof Double))
return ((Number)foo).doubleValue();
else if (foo instanceof Boolean)
return ((Boolean)foo).booleanValue() ? 1 : 0;
else
throw new XmlRpcFault(XmlRpcFault.INVALID_PARAMS,"parameter type mismatch");
} // end getParamDouble
public final String getParamString(int ndx) throws XmlRpcFault
{
Object foo = method_params.get(ndx);
if ((foo instanceof byte[]) || (foo instanceof List) || (foo instanceof Map))
throw new XmlRpcFault(XmlRpcFault.INVALID_PARAMS,"parameter type mismatch");
return foo.toString();
} // end getParamString
} // end class XmlRpcRequest