com.fredhat.gwt.xmlrpc.client.XmlRpcClient.java Source code

Java tutorial

Introduction

Here is the source code for com.fredhat.gwt.xmlrpc.client.XmlRpcClient.java

Source

/*
XMLRPC client for GWT
Copyright (C) 2006
    
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
    
*/

package com.fredhat.gwt.xmlrpc.client;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.xml.client.*;
import com.google.gwt.xml.client.impl.DOMParseException;
import com.google.gwt.http.client.*;

import java.util.*;

/**
 * This class collects information that is specific to the XML-RPC server.  It is
 * then used in an {@link XmlRpcRequest} object to invoke your XML-RPC methods.
 *
 * Quick example:
 * <pre class="code">
 * ...
 * XmlRpcClient client = new XmlRpcClient("http://www.foo.com/xmlrpcserver.php");
 * String methodName = "example.addTwoIntegers";
 * Object[] params = new Object[]{3, 4};
 * 
 * XmlRpcRequest&lt;Integer&gt; request = new XmlRpcRequest&lt;Integer&gt;(
 *          client, methodName, params, new AsyncCallback&lt;Integer&gt;() {
 *       public void onSuccess(Integer response) {
 *          // Handle integer response logic here
 *       }
 * 
 *       public void onFailure(Throwable response) {
 *          String failedMsg = response.getMessage();
 *          // Put other failed response handling logic here
 *       }
 * });
 * request.execute();
 * ...
 * </pre>
 * 
 * 
 * 
 * @author Fred Drake
 * @see XmlRpcRequest
 */
public class XmlRpcClient implements Cloneable {
    /**
     * The default timeout of a XMLRPC request, in milliseconds.
     */
    public static final int DEFAULT_TIMEOUT = 10000;

    private String serverURL;
    private boolean debugMessages;
    private int timeout;
    private String username;
    private String password;

    /**
     * Creates an XMLRPC client with the server URL
     * @param serverURL the full URL of the XMLRPC server of which you wish to connect
     */
    public XmlRpcClient(String serverURL) {
        this.serverURL = serverURL;
        this.debugMessages = false;
        this.timeout = DEFAULT_TIMEOUT;
        this.username = null;
        this.password = null;
    }

    /**
     * Gets the XMLRPC client debug mode.  When in debug mode, the raw request and 
     * response messages will be sent to standard output.  Note that standard output
     * is only displayed when the application is running under hosted mode.
     * @return true if the client is in debug mode, false otherwise.
     */
    public boolean getDebugMode() {
        return debugMessages;
    }

    /**
     * Sets the XMLRPC client debug mode.  When in debug mode, the raw request and 
     * response messages will be sent to standard output.  Note that standard output
     * is only displayed when the application is running under hosted mode.
     * @param debugMode Set true if you want debug mode, false otherwise.
     */
    public void setDebugMode(boolean debugMode) {
        this.debugMessages = debugMode;
    }

    /**
     * Gets the XML-RPC server URL.  Note that this needs to be a full URL (including 
     * the preceding "http://").
     * @return The XML-RPC server URL
     */
    public String getServerURL() {
        return serverURL;
    }

    /**
      * Sets the XML-RPC server URL.  Note that this needs to be a full URL (including 
     * the preceding "http://").
     * @param serverURL The XML-RPC server URL
     */
    public void setServerURL(String serverURL) {
        this.serverURL = serverURL;
    }

    /**
     * Gets the timeout for a method.  If this amount of time has elapsed without a
     * return call from an XML-RPC method, the associated {@link AsyncCallback#onFailure(Throwable)}
     * will be invoked.
     * @return The timeout value in milliseconds
     */
    public int getTimeoutMillis() {
        return timeout;
    }

    /**
     * Gets the username (if one exists) associated with this client.  Only for clients that
     * require authentication.
     * @return The associated username, or null if one does not exist.
     */
    public String getUsername() {
        return username;
    }

    /**
     * Sets the username to be associated with this client.  This is for clients that
     * require authentication.
     * @param username The username for server authentication
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Gets the password (if one exists) associated with this client.  Only for clients that
     * require authentication.
     * @return The associated password, or null if one does not exist.
     */
    public String getPassword() {
        return password;
    }

    /**
     * Sets the password to be associated with this client.  This is for clients that
     * require authentication.
     * @param password The password for server authentication
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Sets the timeout of the XMLRPC request, overiding the 
     * {@link XmlRpcClient#DEFAULT_TIMEOUT} value.  
     * @param timeout The timeout of the request, in milliseconds.
     */
    public void setTimeoutMillis(int timeout) {
        this.timeout = timeout;
    }

    /**
     * Executes an asynchronous XMLRPC call to the server with a specified username
     * and password.  If the execution was successful, the callback's {@link AsyncCallback#onSuccess(Object)} 
     * method will be invoked with the return value as the argument.  If the 
     * execution failed for any reason, the callback's {@link AsyncCallback#onFailure(Throwable)} method will 
     * be invoked with an instance of {@link XmlRpcException} instance as it's argument.
     * @param username the username for authentication
     * @param password the password for authentication
     * @param methodName the name of the XMLRPC method
     * @param params the parameters for the XMLRPC method
     * @param callback the logic implementation for handling the XMLRPC responses.
     * @deprecated As of XMLRPC-GWT v1.1,
     * build an {@link XmlRpcRequest} then call {@link XmlRpcRequest#execute()}
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public void execute(String username, String password, String methodName, Object[] params,
            final AsyncCallback callback) {
        if (methodName == null || methodName.equals("")) {
            callback.onFailure(new XmlRpcException("The method name parameter cannot be null"));
            return;
        }
        if (params == null)
            params = new Object[0];

        Document request = buildRequest(methodName, params);
        if (debugMessages)
            System.out.println("** Request **\n" + request + "\n*************");

        RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.POST, serverURL);
        requestBuilder.setHeader("Content-Type", "text/xml");
        requestBuilder.setTimeoutMillis(timeout);
        if (username != null)
            requestBuilder.setUser(username);
        if (password != null)
            requestBuilder.setPassword(password);

        try {
            requestBuilder.sendRequest(request.toString(), new RequestCallback() {
                public void onResponseReceived(Request req, Response res) {
                    if (res.getStatusCode() != 200) {
                        callback.onFailure(new XmlRpcException("Server returned " + "response code "
                                + res.getStatusCode() + " - " + res.getStatusText()));
                        return;
                    }
                    Object responseObj = buildResponse(res.getText());
                    if (responseObj instanceof XmlRpcException)
                        callback.onFailure((XmlRpcException) responseObj);
                    else
                        callback.onSuccess(responseObj);
                }

                public void onError(Request req, Throwable t) {
                    callback.onFailure(t);
                }
            });
        } catch (RequestException e) {
            callback.onFailure(new XmlRpcException("Couldn't make server request", e));
        }
    }

    /**
     * Executes an asynchronous XMLRPC call to the server.  If the execution was
     * successful, the callback's {@link AsyncCallback#onSuccess(Object)} method will be invoked with the return
     * value as the argument.  If the execution failed for any reason, the callback's
     * {@link AsyncCallback#onFailure(Throwable)} method will be invoked with an instance of {@link XmlRpcException}
     * as it's argument.
     * @param methodName the name of the XMLRPC method
     * @param params the parameters for the XMLRPC method
     * @param callback the logic implementation for handling the XMLRPC responses.
     * @deprecated As of XMLRPC-GWT v1.1,
     * build an {@link XmlRpcRequest} then call {@link XmlRpcRequest#execute()}
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public void execute(String methodName, Object[] params, AsyncCallback callback) {
        this.execute(null, null, methodName, params, callback);
    }

    /**
     * Returns a copy of the {@link XmlRpcClient} object
     */
    public Object clone() {
        XmlRpcClient clientClone = new XmlRpcClient(this.serverURL);
        clientClone.debugMessages = this.debugMessages;
        clientClone.timeout = this.timeout;

        return clientClone;
    }

    // Builds the XML request structure
    // This is legacy logic used in the deprecated execute methods.
    private Document buildRequest(String methodName, Object[] params) {
        Document request = XMLParser.createDocument();
        Element methodCall = request.createElement("methodCall");
        {
            Element methodNameElem = request.createElement("methodName");
            methodNameElem.appendChild(request.createTextNode(methodName));
            methodCall.appendChild(methodNameElem);
        }
        {
            Element paramsElem = request.createElement("params");
            for (int i = 0; i < params.length; i++) {
                Element paramElem = request.createElement("param");
                paramElem.appendChild(buildValue(params[i]));
                paramsElem.appendChild(paramElem);

            }
            methodCall.appendChild(paramsElem);
        }
        request.appendChild(methodCall);

        return request;
    }

    // Builds an individual parameter and sends it back through the StringBuffer
    // This is legacy logic used in the deprecated execute methods.
    private Element buildValue(Object param) {
        Document doc = XMLParser.createDocument();
        Element valueElem = doc.createElement("value");

        if (param == null) {
            Element nilElem = doc.createElement("nil");
            valueElem.appendChild(nilElem);
        } else if (param instanceof Integer) {
            Element intElem = doc.createElement("int");
            intElem.appendChild(doc.createTextNode(param.toString()));
            valueElem.appendChild(intElem);
        } else if (param instanceof Boolean) {
            Element boolElem = doc.createElement("boolean");
            boolean b = ((Boolean) param).booleanValue();
            if (b)
                boolElem.appendChild(doc.createTextNode("1"));
            else
                boolElem.appendChild(doc.createTextNode("0"));
            valueElem.appendChild(boolElem);
        } else if (param instanceof String) {
            Element stringElem = doc.createElement("string");
            stringElem.appendChild(doc.createTextNode(param.toString()));
            valueElem.appendChild(stringElem);
        } else if (param instanceof Double) {
            Element doubleElem = doc.createElement("double");
            doubleElem.appendChild(doc.createTextNode(param.toString()));
            valueElem.appendChild(doubleElem);
        } else if (param instanceof com.fredhat.gwt.xmlrpc.client.Date) {
            com.fredhat.gwt.xmlrpc.client.Date date = (com.fredhat.gwt.xmlrpc.client.Date) param;
            Element dateElem = doc.createElement("dateTime.iso8601");
            dateElem.appendChild(doc.createTextNode(dateToISO8601(date)));
            valueElem.appendChild(dateElem);
        } else if (param instanceof Base64) {
            Element base64Elem = doc.createElement("base64");
            base64Elem.appendChild(doc.createTextNode(param.toString()));
            valueElem.appendChild(base64Elem);
        } else if (param instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) param;
            Element mapElem = doc.createElement("struct");

            for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext();) {
                Object key = iter.next();
                Element memberElem = doc.createElement("member");
                {
                    Element nameElem = doc.createElement("name");
                    nameElem.appendChild(doc.createTextNode(key.toString()));
                    memberElem.appendChild(nameElem);
                }
                {
                    Element innerValueElem = buildValue(map.get(key));
                    memberElem.appendChild(innerValueElem);
                }

                mapElem.appendChild(memberElem);
            }

            valueElem.appendChild(mapElem);
        } else if (param instanceof List) {
            @SuppressWarnings("unchecked")
            List<Object> list = (List<Object>) param;
            Element listElem = doc.createElement("array");
            {
                Element dataElem = doc.createElement("data");
                for (Iterator<Object> iter = list.iterator(); iter.hasNext();) {
                    Element innerValueElem = buildValue(iter.next());
                    dataElem.appendChild(innerValueElem);
                }
                listElem.appendChild(dataElem);
            }

            valueElem.appendChild(listElem);
        }

        return valueElem;
    }

    // Converts a com.gwt.components.client.xmlrpc.Date to the 
    // pseudo ISO8601 date format
    // This is legacy logic used in the deprecated execute methods.
    @SuppressWarnings("deprecation")
    private String dateToISO8601(com.fredhat.gwt.xmlrpc.client.Date date) {
        // The GWT Date object is just a wrapper, 
        // so these deprecated methods are okay.
        int year = date.getYear() + 1900;
        int month = date.getMonth() + 1;
        int day = date.getDate();
        int hour = date.getHours();
        int minute = date.getMinutes();
        int second = date.getSeconds();

        StringBuffer sb = new StringBuffer();
        sb.append(dateString(year, 4)).append(dateString(month, 2));
        sb.append(dateString(day, 2)).append("T");
        sb.append(dateString(hour, 2)).append(":");
        sb.append(dateString(minute, 2)).append(":");
        sb.append(dateString(second, 2));

        return sb.toString();
    }

    // Converts a pseudo ISO8601 date format to a 
    // com.gwt.components.client.xmlrpc.Date object
    // This is legacy logic used in the deprecated execute methods.
    @SuppressWarnings("deprecation")
    private Date iso8601ToDate(String dateString) throws XmlRpcException {
        // We need the format 19980717T14:08:55
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int second;
        com.fredhat.gwt.xmlrpc.client.Date dateObj = new Date();

        if (dateString.length() != 17)
            throw new XmlRpcException("Datetime value " + dateString + " is not in XMLRPC ISO8601 Format");

        try {
            year = Integer.parseInt(dateString.substring(0, 4));
            month = Integer.parseInt(dateString.substring(4, 6));
            day = Integer.parseInt(dateString.substring(6, 8));
            hour = Integer.parseInt(dateString.substring(9, 11));
            minute = Integer.parseInt(dateString.substring(12, 14));
            second = Integer.parseInt(dateString.substring(15));
        } catch (NumberFormatException e) {
            throw new XmlRpcException("Datetime value " + dateString + " is not in XMLRPC ISO8601 Format");
        }

        // The GWT Date object is just a wrapper, 
        // so these deprecated methods are okay.
        dateObj.setYear(year - 1900);
        dateObj.setMonth(month - 1);
        dateObj.setDate(day);
        dateObj.setHours(hour);
        dateObj.setMinutes(minute);
        dateObj.setSeconds(second);

        return dateObj;
    }

    // Parses the servers XML response and constructs the proper return object. 
    // This is legacy logic used in the deprecated execute methods.
    private Object buildResponse(String responseText) {
        if (debugMessages)
            System.out.println("** Response **\n" + responseText + "\n**************");
        Document doc = null;
        try {
            doc = XMLParser.parse(responseText);
        } catch (DOMParseException e) {
            return new XmlRpcException("Unparsable response", e);
        }

        Element methodResponse = doc.getDocumentElement();
        if (!methodResponse.getNodeName().equals("methodResponse"))
            return new XmlRpcException("The document element must be named \"methodResponse\" " + "(this is "
                    + methodResponse.getNodeName() + ")");
        if (getElementNodeCount(methodResponse.getChildNodes()) != 1)
            return new XmlRpcException("There may be only one element under <methodResponse> " + "(this has "
                    + getElementNodeCount(methodResponse.getChildNodes()) + ")");

        Element forkNode = getFirstElementChild(methodResponse);
        if (forkNode.getNodeName().equals("fault")) {
            if (getElementNodeCount(forkNode.getChildNodes()) != 1)
                return new XmlRpcException("The <fault> element must have " + "exactly one child");
            Element valueNode = getFirstElementChild(forkNode);
            Object faultDetailsObj = getValueNode(valueNode);
            if (!(faultDetailsObj instanceof Map))
                return new XmlRpcException("The <fault> element must be a <struct>");
            @SuppressWarnings("unchecked")
            Map<String, Object> faultDetails = (Map<String, Object>) faultDetailsObj;
            if (faultDetails.get("faultCode") == null || faultDetails.get("faultString") == null
                    || !(faultDetails.get("faultCode") instanceof Integer))
                return new XmlRpcException("The <fault> element must contain "
                        + "exactly one <struct> with an integer \"faultCode\" and "
                        + "a string \"faultString\" value");
            int faultCode = ((Integer) faultDetails.get("faultCode")).intValue();
            return new XmlRpcException(faultCode, faultDetails.get("faultString").toString(), null);
        } else if (!forkNode.getNodeName().equals("params"))
            return new XmlRpcException(
                    "The <methodResponse> must contain either " + "a <params> or <fault> element.");

        // No return object is allowed
        Element paramNode = getFirstElementChild(forkNode);
        if (paramNode == null)
            return null;
        if (!paramNode.getNodeName().equals("param") || getElementNodeCount(paramNode.getChildNodes()) < 1)
            return new XmlRpcException("The <params> element must contain " + "one <param> element");
        Node valueNode = getFirstElementChild(paramNode);

        return getValueNode(valueNode);
    }

    // Fetches the proper java object from an XML node
    // This is legacy logic used in the deprecated execute methods.
    private Object getValueNode(Node node) {
        if (!node.getNodeName().equals("value"))
            return new XmlRpcException("Value node must be named <value>, not " + "<" + node.getNodeName() + ">");
        if (getElementNodeCount(node.getChildNodes()) == 0) {
            // If no type is indicated, the type is string.
            String strValue = getNodeTextValue(node);
            return strValue == null ? "" : strValue;
        }
        if (getElementNodeCount(node.getChildNodes()) != 1)
            return new XmlRpcException("A <value> node must have exactly one child");
        Node valueType = getFirstElementChild(node);
        if (valueType.getNodeName().equals("i4") || valueType.getNodeName().equals("int")) {
            String intValueString = getNodeTextValue(valueType);
            if (intValueString == null)
                return new XmlRpcException("Integer child is not a text node");
            try {
                return new Integer(intValueString);
            } catch (NumberFormatException e) {
                return new XmlRpcException("Value \"" + intValueString + "\" is not" + " an integer");
            }
        } else if (valueType.getNodeName().equals("boolean")) {
            String boolValue = getNodeTextValue(valueType);
            if (boolValue == null)
                return new XmlRpcException("Child of <boolean> is not a text node");
            if (boolValue.equals("0"))
                return new Boolean(false);
            else if (boolValue.equals("1"))
                return new Boolean(true);
            else
                return new XmlRpcException("Value \"" + boolValue + "\" must be 0 or 1");
        } else if (valueType.getNodeName().equals("string")) {
            String strValue = getNodeTextValue(valueType);
            if (strValue == null)
                strValue = ""; // Make sure <string/> responses will exist as empty
            return strValue;
        } else if (valueType.getNodeName().equals("double")) {
            String doubleValueString = getNodeTextValue(valueType);
            if (doubleValueString == null)
                return new XmlRpcException("Child of <double> is not a text node");
            try {
                return new Double(doubleValueString);
            } catch (NumberFormatException e) {
                return new XmlRpcException("Value \"" + doubleValueString + "\" is not a double");
            }
        } else if (valueType.getNodeName().equals("dateTime.iso8601")) {
            String dateValue = getNodeTextValue(valueType);
            if (dateValue == null)
                return new XmlRpcException("Child of <dateTime> is not a text node");
            try {
                return iso8601ToDate(dateValue);
            } catch (XmlRpcException e) {
                return new XmlRpcException(e.getMessage());
            }
        } else if (valueType.getNodeName().equals("base64")) {
            String baseValue = getNodeTextValue(valueType);
            if (baseValue == null)
                return new XmlRpcException("Improper XML-RPC response format");
            return new Base64(baseValue);
        } else if (valueType.getNodeName().equals("struct")) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = new HashMap<String, Object>(getElementNodeCount(valueType.getChildNodes()));
            for (int i = 0; i < valueType.getChildNodes().getLength(); i++) {
                if (valueType.getChildNodes().item(i).getNodeType() != Node.ELEMENT_NODE)
                    continue;
                Element memberNode = (Element) valueType.getChildNodes().item(i);
                String name = null;
                Object value = null;
                if (!memberNode.getNodeName().equals("member"))
                    return new XmlRpcException("Children of <struct> may only be " + "named <member>");
                // NodeList.getElementsByTagName(String) does a deep search, so we
                // can legally get more than one back.  Therefore, if the response
                // has more than one <name/> and <value/> at it's highest level,
                // we will only process the first one it comes across.
                if (memberNode.getElementsByTagName("name").getLength() < 1)
                    return new XmlRpcException("A <struct> element must contain " + "at least one <name> child");
                if (memberNode.getElementsByTagName("value").getLength() < 1)
                    return new XmlRpcException("A <struct> element must contain " + "at least one <value> child");

                name = getNodeTextValue(memberNode.getElementsByTagName("name").item(0));
                value = getValueNode(memberNode.getElementsByTagName("value").item(0));
                if (name == null)
                    return new XmlRpcException("The <name> element must " + "contain a string value");
                map.put(name, value);
            }

            return map;
        } else if (valueType.getNodeName().equals("array")) {
            if (getElementNodeCount(valueType.getChildNodes()) != 1)
                return new XmlRpcException("An <array> element must contain " + "a single <data> element");
            Node dataNode = getFirstElementChild(valueType);
            if (!dataNode.getNodeName().equals("data"))
                return new XmlRpcException("An <array> element must contain " + "a single <data> element");
            List<Object> list = new ArrayList<Object>();
            for (int i = 0; i < dataNode.getChildNodes().getLength(); i++) {
                Node valueNode = dataNode.getChildNodes().item(i);
                if (valueNode.getNodeType() != Node.ELEMENT_NODE)
                    continue;
                if (!valueNode.getNodeName().equals("value"))
                    return new XmlRpcException("Children of <data> may only be " + "<value> elements");
                list.add(getValueNode(valueNode));
            }

            return list;
        } else if (valueType.getNodeName().equals("nil")) {
            return null;
        }

        // Not sure what it was supposed to be
        return new XmlRpcException(
                "Improper XML-RPC response format:" + " Unknown node name \"" + valueType.getNodeName() + "\"");
    }

    // Quick utility method to help properly format the pseudo ISO8601 date. 
    // This is legacy logic used in the deprecated execute methods.
    private String dateString(int number, int places) {
        String numStr = new Integer(number).toString();
        StringBuffer sb = new StringBuffer();
        places -= numStr.length();
        for (int i = 0; i < places; i++) {
            sb.append("0");
        }
        sb.append(numStr);

        return sb.toString();
    }

    // Quick utility method to achieve the text value of a node without having to
    // traverse one more level deep each time.  Similar in feel to JDOM's 
    // convenience method.
    // This is legacy logic used in the deprecated execute methods.
    private String getNodeTextValue(Node node) {
        if (node.getChildNodes().getLength() != 1)
            return null;
        Node textNode = (Node) node.getFirstChild();
        if (textNode.getNodeType() != Node.TEXT_NODE)
            return null;

        return textNode.getNodeValue();
    }

    // Fetches the number of element nodes in a node list.
    // This is legacy logic used in the deprecated execute methods.
    private int getElementNodeCount(NodeList nodeList) {
        int elements = 0;
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE)
                elements++;
        }

        return elements;
    }

    // Acts just like Node.getFirstChild() except this returns the first
    // child that is an element node.
    // This is legacy logic used in the deprecated execute methods.
    private Element getFirstElementChild(Node parentNode) {
        NodeList children = parentNode.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE)
                return (Element) node;
        }

        return null;
    }
}