com.madrobot.net.client.XMLRPCClient.java Source code

Java tutorial

Introduction

Here is the source code for com.madrobot.net.client.XMLRPCClient.java

Source

/*******************************************************************************
 * Copyright (c) 2011 MadRobot.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *  Elton Kent - initial API and implementation
 ******************************************************************************/
package com.madrobot.net.client;

import android.util.Base64;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import java.util.Vector;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;

/**
 * XMLRPCClient allows to call remote XMLRPC method.
 * 
 * <p>
 * The following table shows how XML-RPC types are mapped to java call parameters/response values.
 * </p>
 * 
 * <p>
 * <table border="2" align="center" cellpadding="5">
 * <thead>
 * <tr>
 * <th>XML-RPC Type</th>
 * <th>Call Parameters</th>
 * <th>Call Response</th>
 * </tr>
 * </thead>
 * 
 * <tbody>
 * <td>int, i4</td>
 * <td>byte<br />
 * Byte<br />
 * short<br />
 * Short<br />
 * int<br />
 * Integer</td>
 * <td>int<br />
 * Integer</td>
 * </tr>
 * <tr>
 * <td>i8</td>
 * <td>long<br />
 * Long</td>
 * <td>long<br />
 * Long</td>
 * </tr>
 * <tr>
 * <td>double</td>
 * <td>float<br />
 * Float<br />
 * double<br />
 * Double</td>
 * <td>double<br />
 * Double</td>
 * </tr>
 * <tr>
 * <td>string</td>
 * <td>String</td>
 * <td>String</td>
 * </tr>
 * <tr>
 * <td>boolean</td>
 * <td>boolean<br />
 * Boolean</td>
 * <td>boolean<br />
 * Boolean</td>
 * </tr>
 * <tr>
 * <td>dateTime.iso8601</td>
 * <td>java.util.Date<br />
 * java.util.Calendar</td>
 * <td>java.util.Date</td>
 * </tr>
 * <tr>
 * <td>base64</td>
 * <td>byte[]</td>
 * <td>byte[]</td>
 * </tr>
 * <tr>
 * <td>array</td>
 * <td>java.util.List&lt;Object&gt;<br />
 * Object[]</td>
 * <td>Object[]</td>
 * </tr>
 * <tr>
 * <td>struct</td>
 * <td>java.util.Map&lt;String, Object&gt;</td>
 * <td>java.util.Map&lt;String, Object&gt;</td>
 * </tr>
 * </tbody>
 * </table>
 * </p>
 * <p>
 * You can also pass as a parameter any object implementing XMLRPCSerializable interface. In this case your object
 * overrides getSerializable() telling how to serialize to XMLRPC protocol
 * </p>
 */
public class XMLRPCClient extends XMLRPCCommon {
    private HttpClient client;
    private HttpParams httpParams;
    // These variables used in the code inspired by erickok in issue #6
    private boolean httpPreAuth = false;
    private String password = "";
    private HttpPost postMethod;
    private String username = "";

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     */
    public XMLRPCClient(String url) {
        this(URI.create(url));
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     * @param HttpClient
     *            to use
     */
    public XMLRPCClient(String url, HttpClient client) {
        this(URI.create(url), client);
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     */
    public XMLRPCClient(String url, String username, String password) {
        this(URI.create(url), username, password);
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     * @param HttpClient
     *            to use
     */
    public XMLRPCClient(String url, String username, String password, HttpClient client) {
        this(URI.create(url), username, password, client);
    }

    /**
     * XMLRPCClient constructor. Creates new instance based on server URI (Code contributed by sgayda2 from issue #17,
     * and by erickok from ticket #10)
     * 
     * @param XMLRPC
     *            server URI
     */
    public XMLRPCClient(URI uri) {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", new PlainSocketFactory(), 80));
        registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

        postMethod = new HttpPost(uri);
        postMethod.addHeader("Content-Type", "text/xml");

        // WARNING
        // I had to disable "Expect: 100-Continue" header since I had
        // two second delay between sending http POST request and POST body
        httpParams = postMethod.getParams();
        HttpProtocolParams.setUseExpectContinue(httpParams, false);
        this.client = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, registry), httpParams);
    }

    /**
     * XMLRPCClient constructor. Creates new instance based on server URI (Code contributed by sgayda2 from issue #17)
     * 
     * @param XMLRPC
     *            server URI
     * @param HttpClient
     *            to use
     */

    public XMLRPCClient(URI uri, HttpClient client) {
        postMethod = new HttpPost(uri);
        postMethod.addHeader("Content-Type", "text/xml");

        // WARNING
        // I had to disable "Expect: 100-Continue" header since I had
        // two second delay between sending http POST request and POST body
        httpParams = postMethod.getParams();
        HttpProtocolParams.setUseExpectContinue(httpParams, false);
        this.client = client;
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     */
    public XMLRPCClient(URI uri, String username, String password) {
        this(uri);

        ((DefaultHttpClient) client).getCredentialsProvider().setCredentials(
                new AuthScope(uri.getHost(), uri.getPort(), AuthScope.ANY_REALM),
                new UsernamePasswordCredentials(username, password));
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server address
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     * @param HttpClient
     *            to use
     */
    public XMLRPCClient(URI uri, String username, String password, HttpClient client) {
        this(uri, client);

        ((DefaultHttpClient) this.client).getCredentialsProvider().setCredentials(
                new AuthScope(uri.getHost(), uri.getPort(), AuthScope.ANY_REALM),
                new UsernamePasswordCredentials(username, password));
    }

    /**
     * Convenience XMLRPCClient constructor. Creates new instance based on server URL
     * 
     * @param XMLRPC
     *            server URL
     */
    public XMLRPCClient(URL url) {
        this(URI.create(url.toExternalForm()));
    }

    /**
     * Convenience XMLRPCClient constructor. Creates new instance based on server URL
     * 
     * @param XMLRPC
     *            server URL
     * @param HttpClient
     *            to use
     */
    public XMLRPCClient(URL url, HttpClient client) {
        this(URI.create(url.toExternalForm()), client);
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server url
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     */
    public XMLRPCClient(URL url, String username, String password) {
        this(URI.create(url.toExternalForm()), username, password);
    }

    /**
     * Convenience constructor. Creates new instance based on server String address
     * 
     * @param XMLRPC
     *            server url
     * @param HTTP
     *            Server - Basic Authentication - Username
     * @param HTTP
     *            Server - Basic Authentication - Password
     * @param HttpClient
     *            to use
     */
    public XMLRPCClient(URL url, String username, String password, HttpClient client) {
        this(URI.create(url.toExternalForm()), username, password, client);
    }

    /**
     * Convenience method call with no parameters
     * 
     * @param method
     *            name of method to call
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method) throws XMLRPCException {
        return callEx(method, null);
    }

    /**
     * Convenience method call with one parameter
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0) throws XMLRPCException {
        Object[] params = { p0, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with two parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1) throws XMLRPCException {
        Object[] params = { p0, p1, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with three parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2) throws XMLRPCException {
        Object[] params = { p0, p1, p2, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with four parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @param p3
     *            method's 4th parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2, Object p3) throws XMLRPCException {
        Object[] params = { p0, p1, p2, p3, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with five parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @param p3
     *            method's 4th parameter
     * @param p4
     *            method's 5th parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4)
            throws XMLRPCException {
        Object[] params = { p0, p1, p2, p3, p4, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with six parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @param p3
     *            method's 4th parameter
     * @param p4
     *            method's 5th parameter
     * @param p5
     *            method's 6th parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5)
            throws XMLRPCException {
        Object[] params = { p0, p1, p2, p3, p4, p5, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with seven parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @param p3
     *            method's 4th parameter
     * @param p4
     *            method's 5th parameter
     * @param p5
     *            method's 6th parameter
     * @param p6
     *            method's 7th parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6)
            throws XMLRPCException {
        Object[] params = { p0, p1, p2, p3, p4, p5, p6, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with eight parameters
     * 
     * @param method
     *            name of method to call
     * @param p0
     *            method's 1st parameter
     * @param p1
     *            method's 2nd parameter
     * @param p2
     *            method's 3rd parameter
     * @param p3
     *            method's 4th parameter
     * @param p4
     *            method's 5th parameter
     * @param p5
     *            method's 6th parameter
     * @param p6
     *            method's 7th parameter
     * @param p7
     *            method's 8th parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6,
            Object p7) throws XMLRPCException {
        Object[] params = { p0, p1, p2, p3, p4, p5, p6, p7, };
        return callEx(method, params);
    }

    /**
     * Convenience method call with a vectorized parameter (Code contributed by jahbromo from issue #14)
     * 
     * @param method
     *            name of method to call
     * @param paramsv
     *            vector of method's parameter
     * @return deserialized method return value
     * @throws XMLRPCException
     */

    public Object call(String method, Vector paramsv) throws XMLRPCException {
        Object[] params = new Object[paramsv.size()];
        for (int i = 0; i < paramsv.size(); i++) {
            params[i] = paramsv.elementAt(i);
        }
        return callEx(method, params);
    }

    /**
     * Call method with optional parameters. This is general method. If you want to call your method with 0-8
     * parameters, you can use more convenience call() methods
     * 
     * @param method
     *            name of method to call
     * @param params
     *            parameters to pass to method (may be null if method has no parameters)
     * @return deserialized method return value
     * @throws XMLRPCException
     */
    @SuppressWarnings("unchecked")
    public Object callEx(String method, Object[] params) throws XMLRPCException {
        try {
            // prepare POST body
            String body = methodCall(method, params);

            // set POST body
            HttpEntity entity = new StringEntity(body);
            postMethod.setEntity(entity);

            // This code slightly tweaked from the code by erickok in issue #6
            // Force preemptive authentication
            // This makes sure there is an 'Authentication: ' header being send before trying and failing and retrying
            // by the basic authentication mechanism of DefaultHttpClient
            if (this.httpPreAuth == true) {
                String auth = this.username + ":" + this.password;
                postMethod.addHeader("Authorization",
                        "Basic " + Base64.encode(auth.getBytes(), Base64.DEFAULT).toString());
            }

            // Log.d(Tag.LOG, "ros HTTP POST");
            // execute HTTP POST request
            HttpResponse response = client.execute(postMethod);
            // Log.d(Tag.LOG, "ros HTTP POSTed");

            // check status code
            int statusCode = response.getStatusLine().getStatusCode();
            // Log.d(Tag.LOG, "ros status code:" + statusCode);
            if (statusCode != HttpStatus.SC_OK) {
                throw new XMLRPCException("HTTP status code: " + statusCode + " != " + HttpStatus.SC_OK);
            }

            // parse response stuff
            //
            // setup pull parser
            XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
            entity = response.getEntity();
            Reader reader = new InputStreamReader(new BufferedInputStream(entity.getContent()));
            // for testing purposes only
            // reader = new
            // StringReader("<?xml version='1.0'?><methodResponse><params><param><value>\n\n\n</value></param></params></methodResponse>");
            pullParser.setInput(reader);

            // lets start pulling...
            pullParser.nextTag();
            pullParser.require(XmlPullParser.START_TAG, null, Tag.METHOD_RESPONSE);

            pullParser.nextTag(); // either Tag.PARAMS (<params>) or Tag.FAULT (<fault>)
            String tag = pullParser.getName();
            if (tag.equals(Tag.PARAMS)) {
                // normal response
                pullParser.nextTag(); // Tag.PARAM (<param>)
                pullParser.require(XmlPullParser.START_TAG, null, Tag.PARAM);
                pullParser.nextTag(); // Tag.VALUE (<value>)
                // no parser.require() here since its called in XMLRPCSerializer.deserialize() below

                // deserialize result
                Object obj = iXMLRPCSerializer.deserialize(pullParser);
                entity.consumeContent();
                return obj;
            } else if (tag.equals(Tag.FAULT)) {
                // fault response
                pullParser.nextTag(); // Tag.VALUE (<value>)
                // no parser.require() here since its called in XMLRPCSerializer.deserialize() below

                // deserialize fault result
                Map<String, Object> map = (Map<String, Object>) iXMLRPCSerializer.deserialize(pullParser);
                String faultString = (String) map.get(Tag.FAULT_STRING);
                int faultCode = (Integer) map.get(Tag.FAULT_CODE);
                entity.consumeContent();
                throw new XMLRPCFault(faultString, faultCode);
            } else {
                entity.consumeContent();
                throw new XMLRPCException(
                        "Bad tag <" + tag + "> in XMLRPC response - neither <params> nor <fault>");
            }
        } catch (XMLRPCException e) {
            // catch & propagate XMLRPCException/XMLRPCFault
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            // wrap any other Exception(s) around XMLRPCException
            throw new XMLRPCException(e);
        }
    }

    private String methodCall(String method, Object[] params)
            throws IllegalArgumentException, IllegalStateException, IOException {
        StringWriter bodyWriter = new StringWriter();
        serializer.setOutput(bodyWriter);
        serializer.startDocument(null, null);
        serializer.startTag(null, Tag.METHOD_CALL);
        // set method name
        serializer.startTag(null, Tag.METHOD_NAME).text(method).endTag(null, Tag.METHOD_NAME);

        serializeParams(params);

        serializer.endTag(null, Tag.METHOD_CALL);
        serializer.endDocument();

        return bodyWriter.toString();
    }

    /**
     * Convenience Constructor: Sets basic authentication on web request using plain credentials
     * 
     * @param username
     *            The plain text username
     * @param password
     *            The plain text password
     */
    public void setBasicAuthentication(String username, String password) {
        setBasicAuthentication(username, password, false);
    }

    /**
     * Sets basic authentication on web request using plain credentials
     * 
     * @param username
     *            The plain text username
     * @param password
     *            The plain text password
     * @param doPreemptiveAuth
     *            Select here whether to authenticate without it being requested first by the server.
     */
    public void setBasicAuthentication(String username, String password, boolean doPreemptiveAuth) {
        // This code required to trigger the patch created by erickok in issue #6
        if (doPreemptiveAuth = true) {
            this.httpPreAuth = doPreemptiveAuth;
            this.username = username;
            this.password = password;
        } else {
            ((DefaultHttpClient) client).getCredentialsProvider()
                    .setCredentials(new AuthScope(postMethod.getURI().getHost(), postMethod.getURI().getPort(),
                            AuthScope.ANY_REALM), new UsernamePasswordCredentials(username, password));
        }
    }

    /**
     * Amends user agent (Code contributed by mortenholdflod from issue #28)
     * 
     * @param userAgent
     *            defining the new User Agent string
     */
    public void setUserAgent(String userAgent) {
        postMethod.removeHeaders("User-Agent");
        postMethod.addHeader("User-Agent", userAgent);
    }
}