Chat to Bugzilla from Java using Apache XML-RPC

Connecting to the Bugzilla instance from Java code should be relatively simple, since Bugzilla provides an XML-RPC web-service interface (since version 3.0). One of the ways to call XML-RPC methods from Java is by using Apache XML-RPC client classes.

However, the official documentation on the Apache XML-RPC website does not compile and contains // TODO comments. Therefore, it is relatively hard to get started with XML-RPC calls quickly. This post proposes a simple wrapper class with a hint on how to subclass it for individual XML-RPC methods of the Bugzilla web-service.

If you need to quickly grab some sample code, here it is (NetBeans project, optimized for easy reading):

Get the right libraries

First of all, you need to download the proper libraries to get started. You will need Apache XML-RPC libraries.

Add these libraries to your project, no matter what IDE you use.

Include generic XML-RPC call class in your project

The basic class proposed below is meant for simple subclassing for individual XML-RPC methods of the Bugzilla::Webservice::* calls. It should give you a good starting point for generic RPC call implementation. It basically encapsulates Apache XML-RPC client and manages session cookies in a hashmap – in order to perform some calls to Bugzilla via the XML-RPC interface, you need to login to the Bugzilla instance and maintain the session cookies. The class also contains methods to set parameters to the RPC call easily and uses a property to keep the XML-RPC command.

/**
 * @author joshis_tweets
 */
public class BugzillaAbstractRPCCall {
 
    private static XmlRpcClient client = null;
 
    // Very simple cookie storage
    private final static LinkedHashMap<String, String> cookies = new LinkedHashMap<String, String>();
 
    private HashMap<String, Object> parameters = new HashMap<String, Object>();
    private String command;
 
    // path to Bugzilla XML-RPC interface
    private static final String BZ_PATH = "https://localhost/bugzilla/xmlrpc.cgi";
 
    /**
     * Creates a new instance of the Bugzilla XML-RPC command executor for a specific command
     * @param command A remote method associated with this instance of RPC call executor
     */
    public BugzillaAbstractRPCCall(String command) {
        synchronized (this) {
            this.command = command;
            if (client == null) { // assure the initialization is done only once
                client = new XmlRpcClient();
                XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
                try {
                    config.setServerURL(new URL(BZ_PATH));
                } catch (MalformedURLException ex) {
                    Logger.getLogger(BugzillaAbstractRPCCall.class.getName()).log(Level.SEVERE, null, ex);
                }
                XmlRpcTransportFactory factory = new XmlRpcTransportFactory() {
 
                    public XmlRpcTransport getTransport() {
                        return new XmlRpcSunHttpTransport(client) {
 
                            private URLConnection conn;
 
                            @Override
                            protected URLConnection newURLConnection(URL pURL) throws IOException {
                                conn = super.newURLConnection(pURL);
                                return conn;
                            }
 
                            @Override
                            protected void initHttpHeaders(XmlRpcRequest pRequest) throws XmlRpcClientException {
                                super.initHttpHeaders(pRequest);
                                setCookies(conn);
                            }
 
                            @Override
                            protected void close() throws XmlRpcClientException {
                                getCookies(conn);
                            }
 
                            private void setCookies(URLConnection pConn) {
                                String cookieString = "";
                                for (String cookieName : cookies.keySet()) {
                                    cookieString += "; " + cookieName + "=" + cookies.get(cookieName);
                                }
                                if (cookieString.length() > 2) {
                                    setRequestHeader("Cookie", cookieString.substring(2));
                                }
                            }
 
                            private void getCookies(URLConnection pConn) {
                                String headerName = null;
                                for (int i = 1; (headerName = pConn.getHeaderFieldKey(i)) != null; i++) {
                                    if (headerName.equals("Set-Cookie")) {
                                        String cookie = pConn.getHeaderField(i);
                                        cookie = cookie.substring(0, cookie.indexOf(";"));
                                        String cookieName = cookie.substring(0, cookie.indexOf("="));
                                        String cookieValue = cookie.substring(cookie.indexOf("=") + 1, cookie.length());
                                        cookies.put(cookieName, cookieValue);
                                    }
                                }
                            }
                        };
                    }
                };
                client.setTransportFactory(factory);
                client.setConfig(config);
            }
        }
    }
 
    /**
     * Get the parameters of this call, that were set using setParameter method
     * @return Array with a parameter hashmap
     */
    protected Object[] getParameters() {
        return new Object[] {parameters};
    }
 
    /**
     * Set parameter to a given value
     * @param name Name of the parameter to be set
     * @param value A value of the parameter to be set
     * @return Previous value of the parameter, if it was set already.
     */
    public Object setParameter(String name, Object value) {
        return this.parameters.put(name, value);
    }
 
    /**
     * Executes the XML-RPC call to Bugzilla instance and returns a map with result
     * @return A map with response
     * @throws XmlRpcException
     */
    public Map execute() throws XmlRpcException {
        return (Map) client.execute(command, this.getParameters());
    }
}

Subclass the class for individual Bugzilla API calls

This class can be now easily used to implement Bugzilla login. Any other XML-RPC call can be also implemented by subclassing BugzillaAbstractRPCCall class in similar manner:

/**
 *
 * @author joshis
 */
public class BugzillaLoginCall extends BugzillaAbstractRPCCall {
 
    /**
     * Create a Bugzilla login call instance and set parameters 
     */
    public BugzillaLoginCall(String username, String password) {
        super("User.login");
        setParameter("login", username);
        setParameter("password", password);
    }
 
    /**
     * Perform the login action and set the login cookies
     * @returns True if login is successful, false otherwise. The method sets Bugzilla login cookies.
     */
    public boolean login() {
        Map result = null;
        try {
            // the result should contain one item with ID of logged in user
            result = this.execute();
        } catch (XmlRpcException ex) {
            Logger.getLogger(BugzillaLoginCall.class.getName()).log(Level.SEVERE, null, ex);
        }
        return !(result == null || result.isEmpty());
    }
 
}

BugzillaLoginCall class can then be used in following way:

if (new BugzillaLoginCall("admin@example.com", "password").login()) {
    // success
} else {
    // fail
}
Development , ,

2 comments


  1. I’m getting a
    org.apache.xmlrpc.client.XmlRpcHttpTransportException: HTTP server returned unexpected status: Not Found

    What can be happening?
    Thanks in advance!
    -Lucas

    • joshis

      UPDATE: After the mail chat, we discovered that the problem is in BZ version – you must have 3.0+ to be able to use XML-RPC. I have updated the post.

      Wild guesses:

      - are you pointing to the right URL of the BugZilla XML-RPC webservice
      - is the “xmlrpc.cgi” in place + permissions OK?
      - do you pass correct parameters to the BZ remote method (“vector” vs. “object[]“)?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">