05 January 2011

Coding a CXF web service translating a DNA to a protein. My notebook

Apache CXF is a Web Services framework. In this post, I'll will describe how I implemented a Web Service translating a DNA to a protein using the web server Apache Tomcat and the CXF libraries.

Defining the interface

First a simple java interface bio.Translate is needed to describe the service. This simple service receives a string (the dna) and returns a string (the peptide). The annotations will be used by CXF to name the parameters in the WSDL file (see later):
package bio;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
@WebService
public interface Translate
{
@WebResult(name="protein")
public String translate(@WebParam(name = "dna")String dna);
}
view raw Translate.java hosted with ❤ by GitHub

Implementing the service

bio.TranslateImpl implements bio.Translate. The setter/getter for ncbiString will be used by a configuration file to specify a genetic code (standard, mitochondrial) for this service. The methods initIt and cleanUp could be used to acquire and to release some resources for the service when it is created and/or disposed.
package bio;
import javax.jws.WebService;
@WebService(endpointInterface = "bio.Translate",serviceName = "TranslateService",name="Translate")
public class TranslateImpl
implements Translate
{
/** see http://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi */
private String ncbiString=null;
public void setNcbiString(String ncbiString)
{
this.ncbiString=ncbiString;
}
public String getNcbiString()
{
return this.ncbiString;
}
@Override
public String translate(String text)
{
StringBuilder pep=new StringBuilder(text.length()/3);
for(int i=0;i+2< text.length();i+=3)
{
pep.append(translate(text.charAt(i),text.charAt(i+1),text.charAt(i+2)));
}
return pep.toString();
}
private static int base2index(char c)
{
switch(Character.toLowerCase(c))
{
case 't':case 'u': return 0;
case 'c': return 1;
case 'a': return 2;
case 'g': return 3;
default: return -1;
}
}
private char translate(char a,char b,char c)
{
int base1= base2index(a);
int base2= base2index(b);
int base3= base2index(c);
if(base1==-1 || base2==-1 || base3==-1)
{
return '?';
}
else
{
return getNcbiString().charAt(base1*16+base2*4+base3);
}
}
public void initIt()
{
System.err.println("Init called");
}
public void cleanUp()
{
System.err.println("Cleanup called");
}
}

Configuring the service

CXF uses the libraries of the Spring framework (I blogged about spring here ). A XML config file beans.xml makes it easy to configure two java beans for the 'standard genetic code' and the 'mitochondrial code'. In this config file, we also tell Spring about the two methods initIt and cleanUp. Those two beans will be used by two Web Services
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="translateStd" class="bio.TranslateImpl"
init-method="initIt" destroy-method="cleanUp">
<property name="ncbiString">
<value>FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG</value>
</property>
</bean>
<bean id="translateMit" class="bio.TranslateImpl"
init-method="initIt" destroy-method="cleanUp">
<property name="ncbiString">
<value>FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSS**VVVVAAAADDEEGGGG</value>
</property>
</bean>
<jaxws:endpoint
id="translateStdEndPoint"
implementor="#translateStd"
address="/translateStd" />
<jaxws:endpoint
id="translateMitEndPoint"
implementor="#translateMit"
address="/translateMit" />
</beans>
view raw beans.xml hosted with ❤ by GitHub

Defining the CXF application for Tomcat

The following web.xml file only tells tomcat, the web server, to use the CXFServlet to listen to the SOAP queries.
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class> org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub

Compile & Deploy

Installing a CXF web service requires many libraries and at the end, the size of the deployed 'war' file was 8.5Mo(!). Currently, my structure for the current project is:
./translate/WEB-INF/classes/bio/TranslateImpl.java
./translate/WEB-INF/classes/bio/Translate.java
./translate/WEB-INF/beans.xml
./translate/WEB-INF/web.xml
The service was compiled and deployed using the following Makefile:
cxf.lib=apache-cxf-2.3.1/lib
all:
mkdir -p translate/WEB-INF/lib
javac -d translate/WEB-INF/classes -sourcepath translate/WEB-INF/classes translate/WEB-INF/classes/bio/TranslateImpl.java
cp ${cxf.lib}/cxf-2.3.1.jar \
${cxf.lib}/geronimo-activation_1.1_spec-1.1.jar \
${cxf.lib}/geronimo-annotation_1.0_spec-1.1.1.jar \
${cxf.lib}/geronimo-javamail_1.4_spec-1.7.1.jar \
${cxf.lib}/geronimo-servlet_3.0_spec-1.0.jar \
${cxf.lib}/geronimo-ws-metadata_2.0_spec-1.1.3.jar \
${cxf.lib}/geronimo-jaxws_2.2_spec-1.0.jar \
${cxf.lib}/geronimo-stax-api_1.0_spec-1.0.1.jar \
${cxf.lib}/jaxb-api-2.2.1.jar \
${cxf.lib}/jaxb-impl-2.2.1.1.jar \
${cxf.lib}/neethi-2.0.4.jar \
${cxf.lib}/saaj-api-1.3.jar \
${cxf.lib}/saaj-impl-1.3.2.jar \
${cxf.lib}/wsdl4j-1.6.2.jar \
${cxf.lib}/XmlSchema-1.4.7.jar \
${cxf.lib}/xml-resolver-1.2.jar \
${cxf.lib}/aopalliance-1.0.jar \
${cxf.lib}/spring-core-3.0.5.RELEASE.jar \
${cxf.lib}/spring-beans-3.0.5.RELEASE.jar \
${cxf.lib}/spring-context-3.0.5.RELEASE.jar \
${cxf.lib}/spring-web-3.0.5.RELEASE.jar \
${cxf.lib}/commons-logging-1.1.1.jar \
${cxf.lib}/spring-asm-3.0.5.RELEASE.jar \
${cxf.lib}/spring-expression-3.0.5.RELEASE.jar \
${cxf.lib}/spring-aop-3.0.5.RELEASE.jar \
translate/WEB-INF/lib
jar cvf translate.war -C translate .
mv translate.war path-to-tomcat/webapps

Checking the URL

We can see that the service was correctly deployed by pointing a web browser at http://localhost:8080/translate/, where we can see the two services:
Available SOAP services:
Translate
  • translate
Endpoint address: http://localhost:8080/translate/translateMit
WSDL : {http://bio/}TranslateService
Target namespace: http://bio/
Translate
  • translate
Endpoint address: http://localhost:8080/translate/translateStd
WSDL : {http://bio/}TranslateService
Target namespace: http://bio/

Here, the URLs link to the WSDL definition for the web service:
<?xml version="1.0" ?><wsdl:definitions name="TranslateService" targetNamespace="http://bio/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://bio/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<xsd:schema attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://bio/" xmlns:tns="http://bio/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="translate" type="tns:translate"></xsd:element>
<xsd:complexType name="translate">
<xsd:sequence>
<xsd:element minOccurs="0" name="dna" type="xsd:string"></xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="translateResponse" type="tns:translateResponse"></xsd:element>
<xsd:complexType name="translateResponse">
<xsd:sequence>
<xsd:element minOccurs="0" name="protein" type="xsd:string"></xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<wsdl:message name="translate">
<wsdl:part element="tns:translate" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="translateResponse">
<wsdl:part element="tns:translateResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="Translate">
<wsdl:operation name="translate">
<wsdl:input message="tns:translate" name="translate">
</wsdl:input>
<wsdl:output message="tns:translateResponse" name="translateResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="TranslateServiceSoapBinding" type="tns:Translate">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding>
<wsdl:operation name="translate">
<soap:operation soapAction="" style="document"></soap:operation>
<wsdl:input name="translate">
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output name="translateResponse">
<soap:body use="literal"></soap:body>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="TranslateService">
<wsdl:port binding="tns:TranslateServiceSoapBinding" name="TranslatePort">
<soap:address location="http://localhost:8080/translate/translateStd"></soap:address>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

Creating a client

For creating a client consuming this service, I first used the code generated by CXF's wsdl2java but there was a bug with one of the generated classe (it is a known bug feature) so here, I'm going to use the standard ${JAVA_HOME}/bin/wsimport.
> wsimport -p generated -d client -keep "http://localhost:8080/translate/translateStd?wsdl"
parsing WSDL...
generating code...
compiling code...
I wrote a java client MyClient.java using this generated API:
import generated.*;
public class MyClient
{
public static void main(String args[]) throws Exception
{
TranslateService service=new TranslateService();
Translate tr=service.getTranslatePort();
System.out.println(tr.translate("GAATTCATCGATCATAGCATTgcatgctagc"));
}
}
view raw MyClient.java hosted with ❤ by GitHub

Compiling

> cd client
> javac MyClient.java

Running

> java MyClient
EFIDHSIAC*


That's it,

Pierre

No comments: