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 }
I’m getting a
org.apache.xmlrpc.client.XmlRpcHttpTransportException: HTTP server returned unexpected status: Not Found
What can be happening?
Thanks in advance!
-Lucas
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[]“)?