Monday, March 18, 2013

Java, Trac, RPC and JSON

I thought it would be great (and maybe useful for you) to publish here my experience how to set up Trac with RPC over JSON. When I tried to do it from the scratch I had realized that a lot of things work in unexpected way, at least for me.

The main goal is to manage Trac tickets with some application. Here you'll get all information about plugin http://trac-hacks.org/wiki/XmlRpcPlugin. I want to make some details clear for you and show you my ready/worked code.

Trac and Apache


Ok. Short list of items.

  • install trac;
  • install plugin 'TracXMLRPC';
  • enable plugin (tracrpc.* = enabled in trac.ini, see below);
  • set up web server with authorization;
  • create user with 'XML_RPC' privileges in trac;
    • it's really simple, use Basic Authorization (see below green pieces of text);
    • create user ('htpasswd') and grant its to 'XML_PRC' (
      trac-admin /path/to/projenv permission add user_name XML_PRC
      );
  • Also, you maybe would be interested to set up mixed Authorization (fixed files + LDAP), see orange pieces of text below);
  • In Java (see below):
    • do auth;
    • create JSON;
    • send and interpret.

Apache configuration




       
<IfModule wsgi_module>
        WSGIScriptAlias /trac /path_to_trac/trac_site/cgi-bin/trac.wsgi
        WSGIPassAuthorization On
        <Directory /path_to_trac/trac_site/cgi-bin>
                WSGIApplicationGroup %{GLOBAL}
                Require all granted
                AllowOverride None
                Order allow,deny
                Allow from all
        </Directory>
        <Location /commref_req_trac/login>
                AuthBasicProvider file ldap
                AuthType Basic
                #AuthzLDAPAuthoritative off
                AuthUserFile /path_to_trac/trac_env/passwd_file
                AuthName "Trac"
                AuthLDAPURL "ldap://domain.com:389/dc=domain,dc=com?sAMAccountName?sub?(objectClass=user)"
                AuthLDAPBindDN "CN=user_ldap,OU=Service Accounts,OU=GROUP,DC=domain,DC=com"
                AuthLDAPBindPassword "user_ldap_password"
                Require valid-user
        </Location>
</IfModule>
       

Trac configuration


[components]
tracrpc.* = enabled



Java

Transport


How to interact with Trac. For JSON I used  library 'org.json' ('https://github.com/douglascrockford/JSON-java') and 'Apache Commons Codec' ('http://commons.apache.org/proper/commons-codec/') for Base64.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

import org.apache.commons.codec.binary.Base64;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class TracAPI {

    private static int jsonIdSequence = 1;

    public static JSONObject sendJsonRpc(String method, JSONArray parameters) throws IOException, JSONException {
        JSONObject rpc = new JSONObject();
        rpc.put("method", method);
        rpc.put("params", parameters);
        rpc.put("id", jsonIdSequence++);

        String tracUrl = Configuration.getTracApiUrl();
        String tracUsername = Configuration.getTracApiUsername();
        String tracPassword = Configuration.getTracApiPassword();

        URL url = new URL(tracUrl);
        URLConnection conn = url.openConnection();
        String userpass = tracUsername + ":" + tracPassword;
        String basicAuth = "Basic " + new String(new Base64().encode(userpass.getBytes()));
        conn.setRequestProperty ("Authorization", basicAuth);
        conn.setRequestProperty ("Content-Type", "application/json");
        conn.setDoOutput(true);

        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());

        Logger.debug("jsonrpc request:\n" + rpc.toString());

        wr.write(rpc.toString());
        wr.flush();
        wr.close();

        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        String jsonText = "";
        while ((line = rd.readLine()) != null) {
            jsonText += line;
        }
        rd.close();

        Logger.debug("jsonrpc response:\n" + jsonText);

        return new JSONObject(jsonText);
    }

}


Payload

 

read ticket

                    JSONArray parameters = new JSONArray();
                    parameters.put(ticketId); //id
                    JSONObject resp = TracAPI.sendJsonRpc("ticket.get", parameters);
                    String status = resp.getJSONArray("result").getJSONObject(3).getString("status");
                    String resolution = resp.getJSONArray("result").getJSONObject(3).getString("resolution");
                    String owner = resp.getJSONArray("result").getJSONObject(3).getString("owner");

create ticket

                JSONArray parameters = new JSONArray();
                parameters.put("summary text"); //summary
                parameters.put("description text"); //description
                parameters.put(new JSONObject()); //attributes
                parameters.put(false); //notify

                JSONObject resp = TracAPI.sendJsonRpc("ticket.create", parameters);



update/modify ticket

Update must be performed in two stages:
  • Determine (current) version (timestamp) of ticket (to protect ticket from concurrent modifications, you have to indicate which version of ticket you're modifying);
  • Perform modification.


Determine timestamp.

            JSONArray parameters = new JSONArray();
            parameters.put(ticketId); //id
            JSONObject resp = TracAPI.sendJsonRpc("ticket.get", parameters);
            long ts = resp.getJSONArray("result").getJSONObject(3).getLong("_ts");




Update.

            JSONObject attrs = new JSONObject();
            attrs.put("action", "accept");
            attrs.put("_ts", ts);

            parameters = new JSONArray();
            parameters.put(ticketId); // id
            parameters.put("comment text"); // comment
            parameters.put(attrs); // attributes
            parameters.put(false); // notify
            resp = TracAPI.sendJsonRpc("ticket.update", parameters);



Feel free to write comments if you have questions/suggestions or another stuff you'd like to share. Thank you.

Java code was highlighted with "http://markup.su/highlighter/".