| // Licensed to the Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE.txt file distributed with this work for |
| // additional information regarding copyright ownership. The ASF licenses this |
| // file to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| package org.apache.oodt.commons.util; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import org.w3c.dom.*; |
| import org.xml.sax.*; |
| |
| /** XML-RPC utilities. |
| * |
| * This class provides several XML-RPC utilities. |
| * |
| * @author Kelly |
| */ |
| public class XMLRPC { |
| /** Build an XML-RPC method call. |
| * |
| * This may throw {@link IllegalArgumentException} if the <var>params</var> |
| * contains an object that's incompatible with XML-RPC. |
| * |
| * @param name Name of the method to call. |
| * @param params Parameters to pass to the call. |
| * @return An XML string encapsulationg the call. |
| * @throws DOMException If we can't construct the call. |
| */ |
| public static byte[] createMethodCall(String name, Collection params) throws DOMException { |
| Document doc = XML.createDocument(); |
| Element methodCallElement = doc.createElement("methodCall"); |
| doc.appendChild(methodCallElement); |
| XML.add(methodCallElement, "methodName", name); |
| if (params != null && !params.isEmpty()) { |
| Element paramsElement = doc.createElement("params"); |
| methodCallElement.appendChild(paramsElement); |
| for (Iterator i = params.iterator(); i.hasNext();) |
| paramsElement.appendChild(createValueElement(doc, i.next())); |
| } |
| return XML.serialize(doc).getBytes(); |
| } |
| |
| /** Create a <value> element for an XML-RPC method. |
| * |
| * This may throw {@link IllegalArgumentException} if the <var>value</var> is |
| * incompatible with XML-RPC. |
| * |
| * @param doc Owning document. |
| * @param value The value. |
| * @throws DOMException If we can't construct the <value>. |
| */ |
| private static Element createValueElement(Document doc, Object value) throws DOMException { |
| if (value == null) |
| throw new IllegalArgumentException("Nulls not supported in XML-RPC"); |
| Element valueElement = doc.createElement("value"); |
| if (value instanceof Integer || value instanceof Short) { |
| XML.add(valueElement, "int", value.toString()); |
| } else if (value instanceof Boolean) { |
| XML.add(valueElement, "boolean", ((Boolean) value).booleanValue()? "1" : "0"); |
| } else if (value instanceof String) { |
| Element stringElement = doc.createElement("string"); |
| valueElement.appendChild(stringElement); |
| stringElement.appendChild(doc.createCDATASection(value.toString())); |
| } else if (value instanceof Float || value instanceof Double) { |
| XML.add(valueElement, "double", value.toString()); |
| } else if (value instanceof Date) { |
| XML.add(valueElement, "dateTime.iso8601", ISO8601_FORMAT.format((Date) value)); |
| } else if (value instanceof byte[]) { |
| Element base64Element = doc.createElement("base64"); |
| valueElement.appendChild(base64Element); |
| base64Element.appendChild(doc.createCDATASection(new String(Base64.encode((byte[])value)))); |
| } else if (value instanceof Map) { |
| Element structElement = doc.createElement("struct"); |
| valueElement.appendChild(structElement); |
| Map map = (Map) value; |
| for (Iterator i = map.entrySet().iterator(); i.hasNext();) { |
| Element memberElement = doc.createElement("member"); |
| valueElement.appendChild(memberElement); |
| Map.Entry entry = (Map.Entry) i.next(); |
| if (!(entry.getKey() instanceof String)) |
| throw new IllegalArgumentException("Keys in maps for XML-RPC structs must be Strings"); |
| XML.add(memberElement, "name", entry.getKey().toString()); |
| memberElement.appendChild(createValueElement(doc, entry.getValue())); |
| } |
| } else if (value instanceof Collection) { |
| Element arrayElement = doc.createElement("array"); |
| valueElement.appendChild(arrayElement); |
| Element dataElement = doc.createElement("data"); |
| arrayElement.appendChild(dataElement); |
| Collection collection = (Collection) value; |
| for (Iterator i = collection.iterator(); i.hasNext();) |
| dataElement.appendChild(createValueElement(doc, i.next())); |
| } else throw new IllegalArgumentException(value.getClass().getName() + " not supported in XML-RPC"); |
| return valueElement; |
| } |
| |
| /** Parse an XML-RPC method response. |
| * |
| * @param response The response data. |
| * @return The value contained in the <var>response</var>. |
| * @throws XMLRPCFault If the <var>response</var> contained a fault. |
| */ |
| public static Object parseResponse(byte[] response) throws XMLRPCFault { |
| try { |
| DOMParser parser = XML.createDOMParser(); |
| parser.setFeature("http://xml.org/sax/features/validation", false); |
| parser.parse(new InputSource(new ByteArrayInputStream(response))); |
| Document doc = parser.getDocument(); |
| doc.normalize(); |
| XML.removeComments(doc); |
| Element methodResponseElement = doc.getDocumentElement(); |
| if (!"methodResponse".equals(methodResponseElement.getNodeName())) |
| throw new SAXException("Not a <methodResponse> document"); |
| Node child = methodResponseElement.getFirstChild(); |
| if ("params".equals(child.getNodeName())) { |
| return parseValue(child.getFirstChild().getFirstChild()); |
| } else if ("fault".equals(child.getNodeName())) { |
| try { |
| Map map = (Map) parseValue(child.getFirstChild()); |
| throw new XMLRPCFault(((Integer) map.get("faultCode")).intValue(), |
| (String) map.get("faultString")); |
| } catch (ClassCastException ex) { |
| throw new SAXException("XML-RPC <fault> invalid"); |
| } |
| } else throw new SAXException("XML-RPC response does not contain <params> or <fault>"); |
| } catch (SAXException ex) { |
| throw new IllegalArgumentException(ex.getMessage()); |
| } catch (IOException ex) { |
| throw new RuntimeException("Unexpected I/O exception that shouldn't happen, but did: " + ex.getMessage()); |
| } |
| } |
| |
| /** Parse an XML-RPC <value>. |
| * |
| * @param node The <value> node. |
| * @return The Java value of <var>node</var>. |
| */ |
| private static Object parseValue(Node node) { |
| String n = node.getNodeName(); |
| if (!"value".equals(n)) throw new IllegalArgumentException("Expecting a <value>, not a <" + n + ">"); |
| Node t = node.getFirstChild(); |
| n = t.getNodeName(); |
| |
| // Default if there's no nested element is a String. |
| if (t.getNodeType() == Node.TEXT_NODE || t.getNodeType() == Node.CDATA_SECTION_NODE) { |
| return t.getNodeValue(); |
| } |
| |
| // Figure out what the type is from the nested element. |
| String txt = XML.unwrappedText(t); |
| if ("i4".equals(n) || "int".equals(n)) { |
| return new Integer(txt); |
| } else if ("boolean".equals(n)) { |
| if ("1".equals(txt)) return new Boolean(true); |
| else if ("0".equals(txt)) return new Boolean(false); |
| else throw new IllegalArgumentException(n + " does not contain a 0 or 1"); |
| } else if ("string".equals(n)) { |
| return txt; |
| } else if ("double".equals(n)) { |
| return new Double(txt); |
| } else if ("dateTime.iso8601".equals(n)) { |
| try { |
| return ISO8601_FORMAT.parse(txt); |
| } catch (ParseException ex) { |
| throw new IllegalArgumentException(n + " does not contain an ISO8601 format date/time"); |
| } |
| } else if ("base64".equals(n)) { |
| return Base64.decode(txt.getBytes()); |
| } else if ("struct".equals(n)) { |
| Map m = new HashMap(); |
| NodeList memberNodes = t.getChildNodes(); |
| for (int i = 0; i < memberNodes.getLength(); ++i) { |
| Node memberNode = memberNodes.item(i); |
| if (!"member".equals(memberNode.getNodeName())) |
| throw new IllegalArgumentException(n + " contains <" + memberNode.getNodeName() |
| + ">, not <member>"); |
| Node nameNode = memberNode.getFirstChild(); |
| if (nameNode == null || !"name".equals(nameNode.getNodeName())) |
| throw new IllegalArgumentException("<member> missing <name> element"); |
| Node valueNode = nameNode.getNextSibling(); |
| if (valueNode == null || !"value".equals(valueNode.getNodeName())) |
| throw new IllegalArgumentException("<member> missing <value> element"); |
| m.put(XML.unwrappedText(nameNode), parseValue(valueNode)); |
| } |
| return m; |
| } else if ("array".equals(n)) { |
| Node dataNode = t.getFirstChild(); |
| if (dataNode == null || !"data".equals(dataNode.getNodeName())) |
| throw new IllegalArgumentException("<array> missing <data> element"); |
| NodeList children = dataNode.getChildNodes(); |
| List x = new ArrayList(children.getLength()); |
| for (int i = 0; i < children.getLength(); ++i) |
| x.add(parseValue(children.item(i))); |
| return x; |
| } else throw new IllegalArgumentException("Illegal type " + n + " in <value>"); |
| } |
| |
| /** Constructor that causes a runtime exception since this is a utility class. |
| */ |
| private XMLRPC() { |
| throw new IllegalStateException("Do not construct XMLRPC objects"); |
| } |
| |
| /** ISO8601 date format for XML-RPC dates. */ |
| private static DateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); |
| } |