jMOBY Client/Server Quickstart

Motivation

This document is intended to get you up and running using the client-server service communication code (i.e. calling or providing remote Web Services) in the Java implementation (jMOBY) of MOBY-S "Web Services for Molecular Biology". This document assumes that you are familiar with Java and XML. If you are looking for more detailed information on how to query MOBY Central's yellow pages about what services are available, please see this document.

Syntax Notes

Sections


Basic Anatomy of the Java API

Three main classes form the core of the API:

  1. org.biomoby.shared.data.MobyDataObject - any valid data object you want to transmit will be encoded using methods in this class or its subclasses. You never need to handcode any XML (but of course you can if you really want to).
  2. org.biomoby.shared.data.MobyDataObjectSet - for grouping MobyDataObjects together, implements the Java Collection interface, and is the equivalent of the Collection container element in the current MOBY-S API.
  3. org.biomoby.shared.MobyRequest - this class handles all of the network communication (SOAP messaging) required between client and server.

Be sure to include the following statements in the header for any class in which you plan to use jMOBY service communication:

import org.biomoby.client.*;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;


Hello World (or its bioinformatics equivalent)

The following code has two parts. First, a service called "MOBYSHoundGetGenBankff" is found via MOBY Central. This service fetches sequences based on keys such as GenBank gi identifiers. Second, the service is called using MobyRequest with a sample input MobyDataObject. The result of the service invocation (a DNA sequence record in Genbank flat file format) is printed.

            
import org.biomoby.client.*;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;

public class HelloMOBY {
    public static void main(String[] args) throws Exception{

        Central worker = new CentralImpl();
        MobyService templateService = new MobyService("MOBYSHoundGetGenBankff");
        MobyService[] validServices = worker.findService(templateService);

	MobyRequest mr = new MobyRequest(worker);
	mr.setService(validServices[0]);
	mr.setInput(new MobyDataObject("NCBI_gi", "111076"));
	System.out.println(mr.invokeService().toString());
    }
}
	  
A version of this code with comments and error checking is available in the MOBY CVS as Java/src/Clients/TestRequest.java

Creating jMOBY Data

The Java API is designed so that all of the MOBY-S XML concepts can be "get" or "set" using Java primitives (Strings, Numbers, etc.) as parameters to class methods. You do not need to write or parse XML. There are several key features of jMOBY that minimize the number of classes you need to familiarize yourself with:

Because of the input/output symmetry of service data, MOBY objects are highly reusable when you are trying to chain together multiple service discoveries and invocations. If you are interested in creating reusable workflows (MOBY macros of sorts), you may wish to look at Taverna.

Creating a database identifier object

Database identifiers are often the starting point for workflows, as they can be used to retrieve sequence data, which is then visualized or submitted to other services. All MOBY objects have a namespace and an ID, therefore a database identifier is the simplest form of a object. A MobyDataObject constructor is available to easily build database identifiers, MobyDataObject(String namespace, String id):

MobyDataObject dbid = new MobyDataObject("NCBI_gi", "111076");

Creating a "primitive" data object

The following data are considered irreducible, atomic, or "primitive" in MOBY:
MOBY SpecificationjMOBY class in org.biomoby.shared.dataunderlying Java implementationCan be instantiated using any one of
ObjectMobyDataObjectObject
  • "namespace", "id"
  • MOBYDataObject (cloning)
StringMobyDataStringjava.lang.StringBuffer
IntegerMobyDataIntjava.math.BigInteger
  • int
  • java.lang.Number (i.e. Byte, Double, Float, Integer, Long, Short, BigInteger, BigDecimal)
  • Strings like "2005"
FloatMobyDataFloatjava.math.BigDecimal
  • double
  • java.lang.Number
  • Strings like "2.0-e4" or "0.0002"
BooleanMobyDataBooleanjava.lang.Boolean
  • Boolean
  • boolean
  • Strings "true" or "false"
DateTimeMobyDataDateTimejava.util.GregorianCalendar
Any object can also be instantiated using the corresponding XML data as an org.w3c.dom.Element.

Below are some (hopefully) self-explanatory primitive object creation examples:

MobyDataString sequenceString = new MobyDataString("MPGGFILAIDEGTTSARAIIYNQDLEVLGIGQYDFPQHYPSPGYVEHNPDEIWNAQMLAI");
MobyDataInt sequenceLength = new MobyDataInt(60);
MobyDataFloat sequencePI = new MobyDataFloat(3.67);
MobyDataFloat sequenceBlastEValue = new MobyDataFloat("1e-29");
MobyDataBoolean isRepetitive = new MobyDataBoolean(false);
MobyDataDateTime blastDBUpdated = new MobyDataDateTime("2005-03-24");
MobyDataDateTime now = new MobyDataDateTime(Calendar.getInstance());

Creating composite objects

Many times, you will want to send a piece of data that is more complex than a primitive. These can be built using MobyDataComposite, which implements the java.util.Map interface. For example, if a service requires an AminoAcidSequence as input, its two members, SequenceString and Length can be set like so:

String aaSequence = "MPGGFILAIDEGTTSARAIIYNQDLEVLGIGQYDFPQHYPSPGYVEHNPDEIWNAQMLAI";
MobyDataComposite aaSequenceObject = new MobyDataComposite("AminoAcidSequence");
aaSequenceObject.put("SequenceString", new MobyDataString(aaSequence));
aaSequenceObject.put("Length", new MobyDataInt(aaSequence.length()));

Dealing with binary data

Because there is no byte array equivalent in MOBY, all binary data must be encoded as a text string. The most used text encoding is Base64, and jMOBY contains a convenience class (MobyDataBytes) that will encode and decode for you. For example, to create a b64_encoded_gif using the URL based constructor:

String imageLocation = "images/foo.gif";
try{
  MobyDataBytes encodedImage = new MobyDataBytes("myFavoriteImage", 
                                                 getClass().getClassLoader().getResource(imageLocation));
}catch(IOException ioe){
  System.err.println("Could not load image data: " + ioe);
}
or to read the base64 encoded binary image data from a NCBI_Blast_XML_Gif:
// Assume we have a MOBY NCBI_Blast_XML_Gif object in the MobyDataComposite variable called "blastData"
MobyDataBytes encodedImage = (MobyDataBytes) blastData.get("hitGraph");
javax.swing.ImageIcon image = new ImageIcon(encodedImage.getBytes());

Not directly related to jMOBY, but note that ClassLoader.getResource(String) is a convenient way in Java to create URLs appropriate to the context. For example, if the code was in a jar or war, the response would be "jar:bar.jar!images/foo.gif", in an application "file:images/foo.gif", in an applet "http://www.applet.org/base/url/images/foo.gif", etc..


Client/Server Communication (MobyRequest)

We have seen the MobyRequest class in the HelloMOBY example -- it synchronously sent data to a remote Web service and waited for a response. This class can also work asynchronously, performing a callback to a MobyRequestEventHandler when the response is available. Let's recode (available in the CVS Client directory) the HelloMOBY example to work asynchronously:

import org.biomoby.client.*;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;

public class HelloMOBY2 implements MobyRequestEventHandler{
    public static void main(String[] args) throws Exception{

	Central worker = new CentralImpl();
	MobyService templateService = new MobyService("MOBYSHoundGetGenBankff");
        MobyService[] validServices = worker.findService(templateService);

	MobyRequest mr = new MobyRequest(worker);
	mr.setService(validServices[0]);
	mr.setInput(new MobyDataObject("NCBI_gi", "111076"));

	// HelloMOBY2 (ourselves) is a valid listener, must use "new" to
	// have a non-static reference for callback
	mr.invokeService(new HelloMOBY2());

	// Infinite loop broken only by exit in callback function
	while(true){
	   System.err.print("."); // Print a dot every second (poor man's text hourglass)
	   try{Thread.sleep(100);}catch(InterruptedException ie){}
	}
    }

    // Called when the service is about to be invoked.  We have nothing further to do.
    public void start(MobyRequest request, int requestID){
    }  

    // Called by MobyRequest when the response is available
    public void processEvent(MobyRequestEvent mre){
	System.out.println("");	 // Blank line to seperate response from hourglass dots
	System.out.println(mre.getContent().toString());
	System.exit(0);
    }

    // Called after the client-server connection is terminated
    public void stop(MobyRequest request, int requestID){
    }
}

It can also be used by a service provider to parse an incoming request and return the results. More info to come shortly...

Manipulating jMOBY data

One does not only want to create data objects, but also retrieve information from them and edit them. As a client, one may need to display or filter service response data. As a service provider, one needs to extract information from submitted queries. The jMOBY data instance API endeavours to make these data easily extractable and mutable.

Dissecting queries and responses

If we were a service provider who received a MobyDataInstance, we can check its type, then dissect it and extract the fields. Suppose we are a PSI-BLAST provider. We accept AminoAcidSequence:

// ...receive data, a MobyDataInstance called "data"
if(! data.getDataType().getName().equals("AminoAcidSequence")){
   throw new MobyException("Expected an AminoAcidSequence, but instead found " + data.getDataType());
}
String aaSequence = ((MobyDataString) data.get("SequenceString")).toString();
int aaLength = ((MobyDataInt) data.get("Length")).intValue();  // Like java.lang.Number.intValue()
Two notes about this example: 1. eventually we should have a method in MobyDataType with the signature inheritsFrom(MobyDataType), as this would allow us to easily validate input that is a subclass of AminoAcidSequence, and 2. I have assumed that the length of the sequence is not greater than the maximum capacity of a Java integer. If the field I was dealing with may be larger than 2 to the 32nd power, I should call longValue() or even better getObject() which returns an infinite precision BigInteger.

Changing data objects

The getObject method allows the user to retrieve the underlying internal representation of the MOBY data in any MobyDataInstance. The returned Object can be safely cast as listed in the primitives table above, MobyDataComposite uses a HashMap, MobyDataObjectSet uses a Vector. Modifying a mutable returned object (from MobyString, MobyDateTime, or MobyDataBytes) modifies the MobyDataObject from which it came. For example, building on the previous example:

StringBuffer mutableSequence = (StringBuffer) ((MobyDataString) data.get("SequenceString")).getObject();
mutableSequence.replace(0, 2, "XXX");

data.setXmlMode(MobyDataInstance.SERVICE_XML_MODE);
System.out.println(data.toXML());
This would print the sequence XML, with the first three letters of the sequence string being "XXX". Note that you do have to do some casting to get all of these data right. When jMOBY requires Java 1.5, we can get rid of several of these cast annoyances by using the Generics construct.

For classes with unmutable underlying objects (MobyDataInt, MobyDataFloat, and MobyDataBoolean), methods have been added to modify the object values rather than having to build new ones. For example:

mobyBooleanObject.setValue(true);
mobyFloatObject.divide(Math.PI);
mobyIntObject.add(new Integer(2));
The math functions have been written to maintain as much numerical precision as possible.

Comparing Objects

Another convenient thing about the jMOBY objects is that they implement the java.lang.Comparable interface. They have implemented the equals and compareTo methods to be much more forgiving than the underlying data types, which throw exceptions at comparison with anything but their own class. Any object in the jMOBY hierarchy can be compared to any other. If objects values are the same (e.g. the String 2.0 and the float 2.0), the namespace and ID are used to sort. This sorting implementation is very convenient for putting the objects into tabular format.


Creating MOBY XML (responses and queries)

It may at times be necessary to explicitly create a full MOBY XML document (i.e. serialize jMOBY objects). For example to store a response on disk for later usage, or just so you can create your own SOAP response as a service provider. The org.biomoby.shared.data.MobyDataUtils class provides the ability to output XML documents, by specifiying an java.io.OutputStream and the contents.

Create a blank MOBY XML response

Use the one-object convenience constructor for org.biomoby.shared.data.MobyContentInstance. It will automatically create the content envelope. Since the object (first arg) is null, the mobyData will be blank. This data envelope is then made into a proper XML file, with declarations and all, by MobyDataUtils.

MobyDataUtils.toXMLDocument(System.out, new MobyContentInstance(null));

Create a MOBY XML response or query with a single data object

Use the one-object convenience constructor for MobyContentInstance. It will automatically create the full content envelope and embed the XML for your object. The object must be or inherit from MobyDataObject (Simples), or MobyDataObjectSet (Collections) to form a valid content envelope.

MobyDataUtils.toXMLDocument(System.out, new MobyContentInstance(new MobyDataObject("gi", "100089")));

If the service requires a named parameter rather than an anonymous one, the two argument constructor can be used:

MobyDataUtils.toXMLDocument(System.out, new MobyContentInstance(new MobyDataObject("SGD", "S0000221"), "interactorID"));

There are also similar methods in MobyRequest to deal with anonymous and non-anonymous single parameter services:

mobyRequest.setInput(MobyDataObject("gi", "100089"));
     
mobyRequest.setInput(MobyDataObject("SGD", "S0000221"), "interactorID");

Create a MOBY XML response or query envelope with multiple invocations

Use the default constructor for MobyContentInstance, then add at least one MobyDataInstance to it using the java.lang.Map interface or the convenient one-arg put method. To clarify, sending the content block to a service provider is requesting that a service acting on a gi namespace object be invoked for two separate input data. We are not calling a service that takes two MOBY Objects as input.

MobyContentInstance queries = new MobyContentInstance();
queries.put(new MobyDataObject("gi", "100089"));
queries.put(new MobyDataObject("gi", "324442"));
MobyDataUtils.toXMLDocument(System.out, queries);
The one argument put method essentially calls the standard Map.put(Object key, Object value) method with an autogenerated unique key (corresponding to a MOBY queryID). As a service provider you would always want to use the two argument put method to build a response object, as the keys MUST correspond one-to-one with the queryIDs you received from the client.


Parsing MOBY XML

As a counterpart to the utility above, jMOBY provides a method to deserialize a response/request envelope, useful if you are rolling your own server:

MobyContentInstance queries = MobyDataUtils.fromXMLDocument(System.in);
System.out.println("The document contained " + ((queries.size() == 1) ? "1 query" : (query.size() + " queries")));
// Use the HashMap functions to retrieve individual queries...


Paul Gordon
Last modified: Wed Apr 26 07:45:08 MDT 2006