Ridiculously Easy MOBY Service Creation (Ant-less)

What is this?

This tutorial explains (with a start-to-finish example of a meaningful service) how to become a provider of a MOBY service. It should take you less than 30 minutes. The approach is based on extending a base jMOBY Servlet, but does not require you to have any knowledge of Servlets, SOAP, XML, RDF or any of the other technologies underlying its implementation. You do not need to checkout the jMOBY CVS, or install Axis or even Ant.

If on the other hand you are planning on directly hacking the jMOBY classes, the other approach to developing services (using a code generator and the full jMOBY CVS) can be found in this document.

Sections

What are the prerequisites?

The following should be (pre-)installed:

  1. A Java Development Kit version 5.0+
  2. A Java Servlet container, such as Apache Tomcat. If you don't have one setup, here's a very quick guide.
  3. Also download Save them in your code development directory, so they will be convenient to access.

Step 1: Write the function

MobyServlet takes care of all of the protocol issues for you, all you need to do is override the business logic method: processRequest

For example, the following service consumes a MOBY AminoAcidSequence object called "inseq", and returns it reformatted into a MOBY FASTA_AA object called "outseq":
package org.biomoby.service.test; // This could be any package you want in real life...

import org.biomoby.shared.MobyDataType;
import org.biomoby.shared.data.*;
import org.biomoby.service.*;

@mobyService(name="ConvertAAtoFASTA_AA",
             type="FormatConversion", 
	     provider="moby.ucalgary.ca", 
	     author="gordonp@ucalgary.ca",
	     in={"inseq:AminoAcidSequence"},
	     out={"outseq:FASTA_AA"},
	     description={"Converts amino acid objects into FastA formatted records, ", 
			  "primarily to increase inter-service compatibility"})

public class ConvertAAtoFASTA_AA extends MobyServlet{

    public void processRequest(MobyDataJob request, MobyDataJob result) throws Exception{
         // The input parameter for this method is registered as "inseq"
         MobyDataComposite aaSeqObject = (MobyDataComposite) request.get("inseq");

         // SequenceString is a member of incoming AminoAcidSequence object
         MobyDataString aaStringObject = (MobyDataString) aaSeqObject.get("SequenceString");  
         String aaString = aaStringObject.toString();

         // Do the reformatting (divide seq into 50 char chunks per line)
         String fastaString = ">" + aaSeqObject.getId() + "\n" +
                              aaString.replaceAll("(.{1,50})", "$1\n");

         MobyDataType fastaType = MobyDataType.getDataType("FASTA_AA");
         MobyDataComposite fastaObject = new MobyDataComposite(fastaType, 
                                                               aaSeqObject.getPrimaryNamespace(), 
                                                               aaSeqObject.getId(),
                                                               fastaString);

         // Set the result that will be passed back to the client
         result.put("outseq", fastaObject);
    }
}

Note the @mobyService line. It's a Java annotation that is used by the base MobyServlet to do all the input type checking for you. If you've specified the annotation correctly, you are guaranteed that processRequest will only receive syntactically valid data.

That's it for the coding. Really! Now compile it (remember: Java 1.5), including the MobyServlet.war you downloaded in the class path, e.g.:

javac -cp MobyServlet.war:. org/biomoby/service/test/ConvertAAtoFASTA_AA.java

When you go to write your own services with different data types, a quick tutorial on the data API can be found here. All of the datatypes, service types and namespaces you use must be registered in the MOBY Ontologies.

You should probably test that your business logic works! The base servlet has a built-in application test, so just type:

java -cp MobyServlet.war:. org.biomoby.service.test.ConvertAAtoFASTA_AA org.biomoby.service.test.ConvertAAtoFASTA_AA mobyAASeq.xml
and make sure the output is as you expected. Note that the class name is given twice, once to the JVM, and once as an argument to the application itself.

Step 2: Package the code

The default MobyServlet.war business logic is to just return blank MOBY messages. You've got to customize it for your service with the following:

  1. Copy MobyServlet.war to ConvertAAtoFASTA_AA.war
  2. Edit the WAR's WEB-INF/web.xml to tell it about your class (a WAR is just a JAR with a particular directory structure):
    # Extract the existing web.xml from the WAR
    jar xvf ConvertAAtoFASTA_AA.war WEB-INF/web.xml
    # Edit the file with your favorite editor (see the table below)
    vi WEB-INF/web.xml
    

For our example converter, it should minimally look like this, with the required changes to the default web.xml shown in bold:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app 
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>BioMOBY Web Service: ConvertAAtoFASTA_AA</display-name>
    <servlet>
      <servlet-name>ConvertAAtoFASTA_AA</servlet-name>
      <servlet-class>org.biomoby.service.test.ConvertAAtoFASTA_AA</servlet-class>

    </servlet>
    <servlet-mapping>
      <servlet-name>ConvertAAtoFASTA_AA</servlet-name>
      <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

  • Put your new class and the updated web.xml in the WAR file:
    mkdir WEB-INF/classes
    cp org/biomoby/service/test/ConvertAAtoFASTA_AA.class WEB-INF/classes
    jar uvf ConvertAAtoFASTA_AA.war org/biomoby/service/test/ConvertAAtoFASTA_AA.class WEB-INF
    	  
  • The web.xml file is used by the Servlet container (e.g. Tomcat).

    Step 3: Deploy the servlet

    ConvertAAtoFASTA_AA.war now contains your Web Service, and is ready to be deployed in the Servlet container. How you do this depends on the container. For Tomcat, the easiest way is to use the management Web interface (e.g. http://your.servlet.host:8080/manager/html, but change 8080 appropriately if you had to follow Step 0) and upload the WAR.


    1. Specification of WAR Upload

    2. Successful deployment
    If you don't have a nice Web interface available to do this, here are the alternatives (where $NAME is where you installed the container):
    Servlet ContainerWhere to put ConvertAAtoFASTA_AA.warFurther action
    Tomcat $CATALINA_HOME/webapps Restart Tomcat
    WebLogic $WEBLOGIC_HOME/config/mydomain/applications Nothing, it loads the WAR automatically
    JBOSS $JBOSS_HOME/server/default/deploy Nothing, it loads the WAR automatically

    Step 4: Register the service

    You should test your service to make sure it works in the servlet environment. A testing client program is automagically included in your WAR, so type (with the fully qualified host name, and change 8080 appropriately if you had follow Step 0):

    java -jar ConvertAAtoFASTA_AA.war http://your.servlet.host:8080/ConvertAAtoFASTA_AA/ mobyAASeq.xml
    If the service fails, a useful error message should be printed to help you diagnose the problem. This would usually be an incorrect web.xml, or the data type you return isn't what you declared (this is the first occasion your output is thoroughly checked). If you get a java.lang.NoClassDefFoundError, please see the notes below. If that doesn't help, let me know (gordonp @ ucalgary.ca) and I'll try to help you: class paths for servlets can be tricky.

    Finally, if the output looks reasonable (no errors or faults, a real object is returned), you're ready to register your service with MobyCentral so anyone can use it. To do this, simply do the previous command, with an extra argument:

    java -jar ConvertAAtoFASTA_AA.war http://your.servlet.host:8080/ConvertAAtoFASTA_AA/ mobyAASeq.xml register

    This will run the service to make sure it works, then register it with MOBY Central. You'll see the message "Service Successfully Registered!".

    Once it's registered, try loading the example data file used earlier (i.e. mobyAASeq.xml) into Seahawk using the file/URL load icon on the toolbar (). Click the "AminoAcidSequence" hyperlink, and your service will now appear (under the Conversion menu) as a service option!

    Since it isn't useful for everyone who uses this tutorial to host this service, please unregister it now with the following command:

    java -jar ConvertAAtoFASTA_AA.war http://your.servlet.host:8080/ConvertAAtoFASTA_AA/ unregister

    You should get "Service Successfully Unregistered!" if it worked.

    Important Notes

    Permanent services: When you want to deploy your own, permanent service, simply replace register with register_permanent in the earlier command.

    Your service hosts its own RDF specification (retrievable by doing a GET on the servlet URL). An automated agent at MOBY Central retrieves that RDF a few times a day, and if it fails to retrieve it several times in a row, the service is automatically deregistered. Therefore, if you stop or undeploy your servlet for a few days, it will automatically be deregistered. This "passive" deregistration method is for security purposes: no one can (intentionally or accidentally) deregister the service except those who control the servlet container itself.

    Exceptions: Any exceptions or errors your code throws in processRequest will be caught, and included in the MOBY response. They are wrapped in a jMOBY ServiceException and will have the code INTERNAL_PROCESSING_ERROR, and severity level "error". Unless the exception thrown was a jMOBY ServiceException already, in which case the exception is used as-is. Warnings and information can be added without halting the program flow using addException(ServiceException). This example program demonstrates both fatal and non-fatal exceptions.

    Performance: The first call to the service may take a while, but subsequent calls should be considerably faster because the container has all the classes loaded, and the servlet has cached all the ontologies. For example, the basic MobyServlet service call can take about 15 seconds at first, then about 1 second afterwards (using Tomcat).

    Updating your service (if permanently registered): If you need to change your code, simply repeat steps 1, 2 and 3 (plus testing, of course). You do NOT need to register the service, even if you changed the inputs and outputs! This is because MOBY Central's agent will read the new RDF description the Servlet generates. It might take a day or so.

    Syncing with MOBY Central: Your service downloads the MOBY Central ontologies when started, therefore if you want your service to catch an important update to the ontologies, simply restart it (e.g. the "reload" link in the Tomcat manager interface shown previously).

    Using a different MOBY Central: By default, the annotation assumes that the default public Moby Central will be used (this is determined automagically by a combination of hardcoding and fetching info from biomoby.org). If you want to use a different Moby Central, e.g. a test registry, add the centralEndpoint string to the annotation.

    Moving Your Service: The WAR you've built is completely self-contained, therefore you should be able to move your service simply by copying the WAR to another servlet container. You can e-mail it to your friends, so they can mirror the service, or extend it! The only restriction is that the mobyServiceProvider parameter in web.xml must be changed if more than one copy of the service is to be registered (since the "serviceAuthURI:serviceName" combination is a service key in MOBY Central).

    Servlet Initialization: The best way to do any initialization (e.g. database connections) is to override the init() method, being sure to call super.init() first, of course. The thisService variable is not intialized until the first GET or POST to the service. If you for some crazy reason create a constructor method, you must provide a working nullary (no parameter) constructor too. Be sure to free any system resources you allocate in init() by overriding the destroy() method too. It is called when a servlet is being stopped.

    Library Dependencies: As you start to write more complicated services, you may need to use external libraries (e.g. SQL drivers). These must be included in the WAR file before you deploy, like so:

    jar uvf MyService.war WEB-INF/lib/sql.jar


    Paul Gordon
    Last modified: Thu Feb 22 09:20:25 MST 2007