Wednesday, November 17, 2010

Uploading jBPM .par files (from a repository)

Once you've passed the testing cycles during development, you want to make sure that the processes that get deployed onto the production environment are indeed versions that were released according to your formal build procedure - if you have such in place of course.

In our case, that means that the officially released processes are available from a Maven repository. Now there's nothing wrong with retrieving a newly released process archive and using e.g. the jBPM console to upload it. That is, if there's just one such .par file to upload.

My current project produces no less than 16 process archives, one of which is referenced in multiple locations - so it is not uncommon to have more than 20 process instances started during the course of a single request we're processing.

Now regardless of the question whether we chose the right granularity for our processes (which I think we did, of course), this turned into quite some work for each deployment cycle, keeping track of which .par file was deployed and whether it was in the correct sequence (we're not using late binding for sub-processes). Performing this task had become too error-prone to allow it for the production environment.

Ant to the rescue?

The user guide states that there are three ways to deploy the process archives (they forget about the jBPM console altogether there):
  • The process designer tool; an Eclipse plug-in that is part of JBoss Tools. This is of course not a real option, since we want to be able to deploy process archives without having to start up an IDE.
  • The org.jbpm.ant.DeployProcessTask; an Ant task available from the regular jBPM jar file. While an Ant build actually is a good option for a command-line alternative, this particular task is simply too much: it starts up a complete jBPM context for uploading the process directly to the database, and as such requires all of the applicable configuration. I prefer to have as little direct database access from external hosts as possible (e.g. for security considerations), and this approach doesn't accomplish that.
  • Programmatically; using the jBPM API directly. That is basically just more complex than using the Ant task, so that's not the way to go either (in this case).
So unfortunately these suggestions don't give us the ease-of-use that the jBPM console did, just selecting the .par file and clicking the 'Deploy' button, and we had to search a little further.

Reuse the input method of the designer

A closer look at the GPD designer plug-in shows that its upload functionality is little more than an HTTP client, calling the POST method of the ProcessUploadServlet of the jBPM console. This servlet then uses the functionality of the jBPM API (as mentioned above for the Ant task and the programmatic approach). This entrance into the jBPM deployment is exactly what we need: it's simple in just requiring the .par file to be entered, any database interaction is taken care of by the servlet, any security issues can be addressed by the deployment of the console (see e.g. how that's done in the SOA platform).

So, using Apache's HttpClient library I finally came up with something like the following:
package org.jbpm.par;

import java.io.InputStream;
import java.net.URL;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.DefaultHttpClient;

public class ProcessUploader {
    public static void main(String[] args) {
        HttpClient client = null;
        try {
            // Get the input parms: first the file name, then the URL String for its location (in the repo).
            String fileName = args[0];
            URL url = new URL(args[1]);

            // Prepare the request.
            HttpPost request = new HttpPost("http://localhost:8080/jbpm-console/upload");
            ContentBody body = new InputStreamBody(url.openStream(), "application/x-zip-compressed", fileName);
            MultipartEntity entity = new MultipartEntity();
            entity.addPart("bin", body);
            request.setEntity(entity);

            // Execute the request.
            client = new DefaultHttpClient();
            HttpResponse response = client.execute(request);

            // You can examine the the response further by looking at its contents:
            InputStream is = response.getEntity().getContent(); // And e.g. print it to screen...
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (client != null) {
                // Clean up after yourself.
                client.getConnectionManager().shutdown();
            }
        }
    }
}
While this simple example takes a single URL for a .par file (along with the corresponding file name) on the command line, we'll be using the same principle with a standard properties file listing all of the URLs for our process archives and looping through that list executing a request for each file. And these URLs will be pointing to our Maven repository, of course, allowing us to configure the correct versions for each release.

Note that the URL for the upload servlet is hard-coded in the example; if you're uploading your .par files from a different host, you'd want to configure the host on which jBPM runs differently than 'localhost', of course.