and debugged the handling of parameter serialization so that I think it can actually pass now...
467 lines
14 KiB
Java
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
|