HCSConnect, SOAP and images – the sordid details

Over the past few months I’ve been trying to work with the HCSConnect web service API that Cellomics provides as a means to programmatically access the Arrayscan image and data store. While the API works for getting things such as plate names, I was unable to retrieve images. After much poking and some suggestions from Rossella Rispoli (ICR), I put together a solution. Which (I think) is essentially a kludge due to the fact that the WSDL generated by the HCSConnect API is buggy.

The API is based on SOAP (yuk!) and so the first thing to do is to autogenerate a client. Since I work with Java, I used the AXIS libraries. Importantly, it seems that the only combination that works is AXIS1 with the BasicHTTPBinding (SOAP 1.1) from the API. AXIS2 + WSBinding (SOAP 1.2) do not work – I don’t know why.

Assuming you’ve got the AXIS1 libraries somewhere, here’s the sequence of steps to get a SOAP client generated:

1
2
3
4
5
6
7
8
9
10
11
export AXIS_HOME=/path/to/axis
export AXIS_LIB=$AXIS_HOME/lib
export AXISCLASSPATH=$AXIS_LIB/axis.jar:$AXIS_LIB/commons-discovery-0.2.jar:$AXIS_LIB/commons-logging-1.0.4.jar:$AXIS_LIB/jaxrpc.jar:$AXIS_LIB/saaj.jar:$AXIS_LIB/log4j-1.2.8.jar:$AXIS_LIB/wsdl4j-1.5.1.jar

java -cp "$AXISCLASSPATH" org.apache.axis.wsdl.WSDL2Java -p gov.nih.ncgc.arrayscan -B http://api.host.name:2020/?wsdl

mkdir arrayscan
mv build.xml gov arrayscan
cd arrayscan
ant
mv "?wsdl.jar" HCSConnect-client-axis1.jar

You’ll probably want to change the package name. Also note the use of port 2020 – this corresponds to the HTTP service. The resultant JAR file is what you should add to your project as a dependency. This library lets you connect to the API and start pulling data.

But it will fail when you try to retrieve an image. And this because, the SOAP response that is returned when an image is requested provides the binary image data as an attachment. The autogenerated client code is unable to handle this (suggesting an issue in the WSDL specification for that method).

To get around this, I needed to intercept the SOAP response and extract the image bytes myself. This can be achieved by creating an implementation of GenericHandler that only deals with the response from the GetImage method of the HCSConnect API. The code below does this and when it sees such a response, it extracts the image bytes and stores it in a static variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class GetImageMessageHandler extends GenericHandler {
 
    HandlerInfo headerInfo;
    public static byte[] imageBytes;
 
    public void init(HandlerInfo info) {
        headerInfo = info;
    }
 
    public QName[] getHeaders() {
        return headerInfo.getHeaders();
    }
 
    public boolean handleResponse(MessageContext context) {
 
        try {
 
            // get the soap header
            SOAPMessage message = ((org.apache.axis.MessageContext) context).getMessage();
            String opName = ((org.apache.axis.MessageContext) context).getOperation().getName();
            if (!opName.equals("GetImage")) return true;
 
            int nattach = 0;
            Iterator iter = message.getAttachments();
            while (iter.hasNext()) {
                Object next = iter.next();
                nattach++;
            }
            if (nattach != 1) throw new RuntimeException("If operation is GetImage, we must have 1 attachment");
 
            // Create transformer
            TransformerFactory tff = TransformerFactory.newInstance();
            Transformer tf = tff.newTransformer();
 
            // Get reply content
            Source sc = message.getSOAPPart().getContent();
 
            // Set output transformation
            StreamResult result = new StreamResult(System.out);
            tf.transform(sc, result);
            System.out.println();
 
            iter = message.getAttachments();
            while (iter.hasNext()) {
                AttachmentPart att = (AttachmentPart) iter.next();
                BufferedInputStream bis = new BufferedInputStream(att.getDataHandler().getInputStream());
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int c;
                while ((c = bis.read()) != -1) baos.write(c);
                bis.close();
                imageBytes = baos.toByteArray();
            }
 
        } catch (Exception e) {
            throw new JAXRPCException(e);
        }
        return true;
    }
 
    public boolean handleRequest(MessageContext context) {
        // return true to continue message processing
        return true;
    }
}

With this in hand, code that want’s to retrieve the image can use it as follows

1
2
3
4
5
6
HCSConnectLocator csl = new HCSConnectLocator();
HandlerRegistry hr = csl.getHandlerRegistry();
List hc = hr.getHandlerChain(new QName("http://schemas.datacontract.org/2004/07/Thermo.Connect", "HTTPHCSConnect"));
HandlerInfo hi = new HandlerInfo();
hi.setHandlerClass(GetImageMessageHandler.class);
hc.add(hi);

and then instead of doing

1
Image image = csl.getHTTPHCSConnect().getImage(idr);

you’d do

1
2
3
4
5
6
7
try {
  csl.getHTTPHCSConnect().getImage(idr);
} catch (AxisFault e) {
  FileOutputStream imageOutFile = new FileOutputStream("img.jpg");
  imageOutFile.write(GetImageMessageHandler.imageBytes);
  imageOutFile.close();
}

This is somewhat inelegant as the use of the static variable in GetImageMessageHandler means that we can’t use this class in a multi-threaded environment. However, it appears that the AXIS API instantiates instances of the handler class itself, rather than accepting an instance, so I don’t see an easy way around this.

Leave a Reply

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