Monday, November 30, 2009

Minimal jars required for Axis2 1.5

Minimal jars required for Axis2 1.5 is slightly different from Axis2 1.4.1.

Here is a set of jars required with Axis2 1.5 to invoke a service written using Axiom and
to invoke it using direct service client using default axis2.xml file.

axis2-kernel-1.5.jar
axiom-api-1.2.8.jar
axiom-dom-1.2.8.jar
axiom-impl-1.2.8.jar
commons-logging-1.1.1.jar
wsdl4j-1.6.2.jar
XmlSchema-1.4.3.jar
neethi-2.0.4.jar
axis2-transport-local-1.5.jar
axis2-transport-http-1.5.jar
commons-httpclient-3.1.jar
mail-1.4.jar
commons-fileupload-1.2.jar
woden-api-1.0M8.jar
woden-impl-dom-1.0M8.jar
httpcore-4.0.jar
commons-codec-1.3.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
wstx-asl-3.2.4.jar


axis2-jaxws-1.5.jar may be used to avoid some waning messages with the default axis2.xml.

Monday, October 5, 2009

Axis2 Class loading

Axis2 let users to deploy services and modules as separate archive files. These files can contain normal java classes or .jar files. Therefore at the deployment time Axis2 has to read those java classes and .jar files and create class loaders for services and modules. These are the places it looks for load those classes.
When axis2 deployed as web application using the axis2 war distribution, Axis2 uses the web application class loader (this contains WEB-INF/classes and WEB-INF/lib jars) as the top most parent.

For service deployment it creates a service class loader using jars under the services/lib (this folder has to create by the users if needed) folder and using web application class loader as the parent. Then for each service it creates a separate class loader (which contains archive class files and lib jar files) using the service class loader as the parent.

Similar thing happens for modules. The module class loader can uses the modules/lib folder to load the jar files and it use as the parent class loader for other modules. This forms the following parent child relationship for class loaders.

Web class loader
services class loader modules class loader
other service class loaders other service class loaders

Child First (parent last) class loading
By default Axis2 uses the parent first class loading. Child first class loading can be switch by setting EnableChildFirstClassLoading parameter.
<parameter name="EnableChildFirstClassLoading">true</parameter>

Thread Context class loading
Some libraries uses the Thread context class loading. By default axis2 sets the service class loader as the thread context class loader before invoking a service. This can be changed by setting the context class loader to composite.

I.e adding this parameter to service
<parameter name="ServiceTCCL">composite</parameter>

when this parameter is set it creates the context class loader by using both existing thread context class loader and service class loader.

Thursday, October 1, 2009

Axis2 Hierarchical Service Deployment

Axis2 has a concept of a service archive (.aar). After introducing the custom deployers even users can deploy different types of files as services. (eg. .class and .jar for jaxws services).
But until recently all those service files had to kept in the services folder or respective other service folders. This may not efficient in managing a lot of services. With the introduction of hierarchical service deployment it is possible to keep the directory structure under the services folder and the folder structure is reflect in the service epr as well.

eg. one can have services like this.

./services/interop/rm/EchoStringService.aar

./services/interop/rm/PingService.aar
./services/version/v1.1/version.aar

./services/version/v1.2/version.aar


Epr for the PingService is
http://localhost:8080/axis2/services/interop/rm/PingService

As seen in the last two lines this feature can be used for version management as well. Different version of the same service can be deployed and Epr is generated accordingly.

http://localhost:8080/axis2/services/version/v1.2/Version

Wednesday, September 30, 2009

Inter operating Apache Sandesah2 with WCF

Recently I have been working on interoperating WCF with the Apache Sandesha2. The good news is all the plain RM scenarios pass with some small fixings. Here is an sample client which can be used to invoke the wcf services available at http://131.107.72.15/endpoints/

First I generated the code for the given wsdl with wsdl2java tool with the following arguments

-uri http://10.100.1.238/ReliableMessaging_Service_WSAddressingAugust2004_Indigo/RequestReply.svc?wsdl
-u -ap -uw

-p org.wso2.carbon.interop.microsoft.rm.requestreply

-o target/generated-code

-Emp org.wso2.carbon.interop.microsoft.rm.requestreply

-ns2p http://tempuri.org/=org.wso2.carbon.interop.microsoft.rm.requestreply,http://schemas.microsoft.com/2003/10/Serialization/=org.wso2.carbon.interop.microsoft.rm.requestreply.types

Then we can simple invoke the service using the following client code.

// creating an configuration context which contains sandesah2 and addressing
ConfigurationContext configurationContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem("target" + File.separator +
"org.wso2.carbon.interop.microsoft.rm-SNAPSHOT.dir" +
File.separator + "repository");

EchoStringServiceCustomBinding_IEchoString9Stub stub = new EchoStringServiceCustomBinding_IEchoString9Stub(configurationContext,
"http://131.107.72.15/ReliableMessaging_Service_WSAddressingAugust2004_Indigo/RequestReply.svc/Reliable_Anonymous_Soap11_WSAddressingAugust2004_RM11");
// this is to send messages through tcp mon
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.TRANSPORT_URL,
"http://localhost:8088/ReliableMessaging_Service_WSAddressingAugust2004_Indigo/RequestReply.svc/Reliable_Anonymous_Soap11_WSAddressingAugust2004_RM11");

// setting the correct addressing , soap and rm versions
stub._getServiceClient().getOptions().setProperty(AddressingConstants.WS_ADDRESSING_VERSION, AddressingConstants.Submission.WSA_NAMESPACE);
stub._getServiceClient().getOptions().setSoapVersionURI(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
stub._getServiceClient().getOptions().setProperty(SandeshaClientConstants.RM_SPEC_VERSION, Sandesha2Constants.SPEC_VERSIONS.v1_1);

//wcf does not support make connections so disable it
Parameter sandeshaPolicyBeanParam = stub._getServiceClient().getAxisConfiguration().getParameter(Sandesha2Constants.SANDESHA_PROPERTY_BEAN);
if (sandeshaPolicyBeanParam != null) {
SandeshaPolicyBean sandeshaPolicyBean = (SandeshaPolicyBean) sandeshaPolicyBeanParam.getValue();
sandeshaPolicyBean.setEnableMakeConnection(false);
}

stub._getServiceClient().engageModule("addressing");
stub._getServiceClient().engageModule("sandesha2");

PingRequestBodyType pingRequestBodyType = new PingRequestBodyType();
pingRequestBodyType.setSequence("Sequence 1");
pingRequestBodyType.setText("testparam");
PingResponseBodyType pingResponseBodyType = null;
String responseString = "";
for (int i = 0; i < 3; i++) {
responseString += "testparam";
pingResponseBodyType = stub.echoString(pingRequestBodyType);
}

// after sending the messages we can close and terminate the sequence
SandeshaClient.closeSequence(stub._getServiceClient());
SandeshaClient.terminateSequence(stub._getServiceClient());

//wait until terminate sequence messge is send
SandeshaClient.waitUntilSequenceCompleted(stub._getServiceClient());

Sunday, September 27, 2009

Handling date and dateTime with Axis2

With Axis2 1.5 java.util.Date is mapped to xs:date type and java.util.Calendar is mapped to xs:dateTime. For an example if we deployed a POJO service as follows

public class TestService {

public Date getDate() {
return new Date();
}

public Calendar getCalendar() {
return Calendar.getInstance();
}
}

This generates an WSDL with schema like this.

<xs:element name="getDateResponse">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="return" nillable="true" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getCalendarResponse">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="return" nillable="true" type="xs:dateTime"/>
</xs:sequence>
</xs:complexType>
</xs:element>
When invoking the service we get the following response xmls

<ns:getDateResponse xmlns:ns="http://service.lockhead.test"><ns:return>2009-09-28</ns:return></ns:getDateResponse>
<ns:getCalendarResponse xmlns:ns="http://service.lockhead.test"><ns:return>2009-09-28T10:55:01.770+05:30</ns:return></ns:getCalendarResponse>

Thursday, September 24, 2009

Sending an arbitrary soap message with Axis2

Axis2 code generation strictly follows the WS-I profile which prohibits sending soap messages with two child elements in the soap body. Even normal Service client API allows only one omElement to be send at the soap body.
One work around to send two elements is to use an operation client which allows a user to set whole SoapEnvelope hence sending any arbitrary one.

ServiceClient serviceClient = new ServiceClient();
OperationClient opClient = serviceClient.createClient(ServiceClient.ANON_OUT_IN_OP);
//creating message context
MessageContext outMsgCtx = new MessageContext();
Options opts = outMsgCtx.getOptions();
//setting properties into option
opts.setTo(new EndpointReference("http://localhost:8088/axis2/services/TestInOutService"));
opts.setAction("urn:TestInOutService");

SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory();
SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();

OMElement omElement1 = soapFactory.createOMElement(new QName("http://axis2.test","firstElement"));
OMElement omElement2 = soapFactory.createOMElement(new QName("http://axis2.test","secondElement"));

soapEnvelope.getBody().addChild(omElement1);
soapEnvelope.getBody().addChild(omElement2);

outMsgCtx.setEnvelope(soapEnvelope);
opClient.addMessageContext(outMsgCtx);
opClient.execute(true);

MessageContext inMsgtCtx = opClient.getMessageContext("In");

SOAPEnvelope response = inMsgtCtx.getEnvelope();
System.out.println(response);

Wednesday, May 20, 2009

Axis2 child first class loading

With axis2 it is possible to deploy services as service archive files and modules as module archive files. Both these two types of archives can contain .class or .jar files within it. Axis2 creates a separate class loader to load these class and jar files within the archive file.
By default Axis2 uses parent first class loading. This means jvm search for a particular class in parent before search it at the current class loader. As a result of this user could not use a different version of a jar file within their archives.
I recently added the child first class loading support (this is only available with main trunk) so that users can use different versions of class files at their services.

How to enable it?
This can be globally enable by setting the 'EnableChildFirstClassLoading' parameter to true at the axis2.xml file. Further the behavior of the services or modules can be set adding the same parameter to services.xml and module.xml.

How it works?
Axis2 uses a custom class loader called DeploymentClassLoader which is extended from the java URLClassloader. URLClassloader uses a parent first approach. LoadClass method of the java classloader class looks like this,

protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

it first checks the parent class loader to check the class and child class loader after that. We can revise this order simply overriding this method at the DeploymentClassLoader like this,
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class c = null;
if (!isChildFirstClassLoading) {
c = super.loadClass(name, resolve);
} else {
c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (Exception e) {
c = super.loadClass(name, resolve);
}
}
}
return c;
}

Here the isChildFirstClassLoading is set according to the parameters uses set above.

Thursday, May 7, 2009

Improving Axis2 client http transport performance

Although Axis2 supports a many transports http is the highly used one. Axis2 uses Apache HttpClient to implement its http transports sender. By default Axis2 creates a new http client object per invocation. This causes a new MultiThreadedHttpConnectionManager object being created for each invocation. As a result axis2 by default does not use the http 1.1 keep alive feature as well.
The solution for this problem is to re use the same HttpClient and MultiThreadedHttpConnectionManager per one thread. Axis2 client invocation may happen using one thread or using multiple threads. After reusing the MultiThreadedHttpConnectionManager has to shut down to release connections. If not this can lead to multiple tcp CLOSE_WAIT connections situation.

Following code shows this settings.

// creates a new connection manager and a http client object
MultiThreadedHttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
HttpClient httpClient = new HttpClient(httpConnectionManager);

try {

client = new ServiceClient(null, null);
Options opts = new Options();
client.setOptions(opts);
opts.setTo(new EndpointReference("http://localhost:8085/axis2/services/TestInOutService"));
opts.setAction("urn:TestInOutService");

OMElement payload = buildSoapObject(
"" +
"IBM" +
"
"
);
System.out.println("Sending the request ...." + System.currentTimeMillis());


// set the above created objects to re use.
client.getOptions().setProperty(HTTPConstants.REUSE_HTTP_CLIENT, Constants.VALUE_TRUE);
client.getOptions().setProperty(HTTPConstants.CACHED_HTTP_CLIENT, httpClient);

// since we reuse the same http client and conneciton manager all these http messages
// will go in one tcp connection.
// this can be verified by sending the messages through tcpmon.
for (int i = 0; i < 10; i++) {
OMElement response = client.sendReceive(payload);
response.build();
}


} catch (Exception e) {
e.printStackTrace();
}
finally {
if (client != null) {
try {
client.cleanupTransport();
} catch (Exception e) {
e.printStackTrace();
}

try {
client.cleanup();
} catch (Exception e) {
e.printStackTrace();
}
}

}
try {
Thread.sleep(1000);
} catch (Exception e) {// do nothing}
}
httpConnectionManager.closeIdleConnections(0);
httpConnectionManager.shutdown();

Monday, February 16, 2009

Accessing WSO2 products back end directly

Recently wso2 release a set of products which address all aspects of any SOA solution. All these products are build upon a platform called WSO2 carbon which is base on OSGI. One of the common features of them is to ability to run as Front End (FE) and Back End (BE) services. FE server contains all the GUI components and BE server expose the back end functionality as web services. Therefore in a typical Adminconsole usage scenario client application runs on the browser makes servlet request to FE server and FE server makes a web service call to BE server.
As it can be seen it is possible to access these BE serer web services directly using web service clients generated using the respective WSDL files for the service. The following example code provides a code for such a client which try to directly access the WS02 registry back end services to put a resource and retrieve it back. Generally this code segment show how one can access a service using https transport and mange cookies between many stubs.

try {

// set the system properties to enable the https conection
System.setProperty("javax.net.ssl.trustStore", "src/test/resources/wso2carbon.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");

// first authenticate the client.
AuthenticationAdminServiceStub authenticationStub =
new AuthenticationAdminServiceStub("https://10.100.1.104:9443/services/AuthenticationAdminService");
authenticationStub._getServiceClient().getOptions().setManageSession(true);
boolean loggedIn = authenticationStub.login("admin", "admin", NetworkUtils.getLocalHostname());

// get the cooke to use in the next service invoations. This lets registry service to authenticate
// the second request
ServiceContext serviceContext = authenticationStub._getServiceClient().getLastOperationContext().getServiceContext();
String sessionCookie = (String) serviceContext.getProperty(HTTPConstants.COOKIE_STRING);
// print the cookie
System.out.println("session Cookie " + sessionCookie);

// doing the registry update
ResourceAdminServiceStub stub = new ResourceAdminServiceStub("https://10.100.1.104:9443/services/ResourceAdminService");
stub._getServiceClient().getOptions().setManageSession(true);
stub._getServiceClient().getOptions().setTimeOutInMilliSeconds(60000000);
// create a path to add new resource
String path = "/testfolder/axis2.zip";
String mediaType = "application/xml";
String description = "test service";
// enable MTOM and set the previous cookie
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_MTOM, Constants.VALUE_TRUE);
stub._getServiceClient().getOptions().setProperty(HTTPConstants.COOKIE_STRING, sessionCookie);
DataHandler dataHandler = new DataHandler(new FileDataSource("/home/amila/services.xml"));
// adding the resource
stub.addResource(path, mediaType, description, dataHandler);

// getting content
ContentDownloadBean contentDownloadBean = stub.getContentDownloadBean("/testfolder/testxml3");
DataHandler response = contentDownloadBean.getContent();
FileOutputStream fileOutputStream = new FileOutputStream("/home/amila/new_services2.xml");
response.writeTo(fileOutputStream);
fileOutputStream.flush();
} catch (AxisFault axisFault) {
axisFault.printStackTrace();
} catch (java.rmi.RemoteException e) {
e.printStackTrace();
} catch (org.wso2.carbon.registry.mgt.ui.resource.services.ExceptionException0 exceptionException0) {
exceptionException0.printStackTrace();
} catch (org.wso2.carbon.core.services.authentication.AuthenticationExceptionException0 authenticationExceptionException0) {
authenticationExceptionException0.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

Saturday, January 3, 2009

Using MTOM with Axis2

MTOM provides a way to send binary data as it is without encoding it to a text format. Axis2 ADB has built in support for MTOM. Therefore it is almost a trivial task to send and receive binary data using axis2.

Here is an sample wsdl

<definitions xmlns:tns="http//tempuri.org/sample"
xmlns:ns1="http//tempuri.org/sample/types"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http//tempuri.org/sample">
<types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http//tempuri.org/sample/types"
targetNamespace="http//tempuri.org/sample/types"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="echoBinaryData">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="param" type="xsd:base64Binary"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="echoBinaryDataResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="param" type="xsd:base64Binary"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>

<message name="EchoBinaryDataRequest">
<part name="part1" element="ns1:echoBinaryData"/>
</message>
<message name="EchoBinaryDataResponse">
<part name="part1" element="ns1:echoBinaryDataResponse"/>
</message>

<portType name="SamplePortType">
<operation name="echoBinaryData">
<input message="tns:EchoBinaryDataRequest"/>
<output message="tns:EchoBinaryDataResponse"/>
</operation>
</portType>

<binding name="SampleSoap11Binding" type="tns:SamplePortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="echoBinaryData">
<soap:operation style="document" soapAction="urn:echoBinaryData"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<binding name="SampleSoap12Binding" type="tns:SamplePortType">
<soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="echoBinaryData">
<soap12:operation style="document" soapAction="urn:echoBinaryData"/>
<input>
<soap12:body use="literal"/>
</input>
<output>
<soap12:body use="literal"/>
</output>
</operation>
</binding>

<service name="SampleService">
<port name="Soap11Port" binding="tns:SampleSoap11Binding">
<soap:address location="http://localhost:8080/axis2/services/SampleService"/>
</port>
<port name="Soap12Port" binding="tns:SampleSoap12Binding">
<soap:address location="http://localhost:8080/axis2/services/SampleService"/>
</port>
</service>
</definitions>

then both client and server side code can be generated with the wsdl2java tool.

it sends and receives a binary file. Following client and server side code can be used to send and receive binary data.
try {
SampleServiceStub stub = new SampleServiceStub("http://localhost:8080/axis2/services/SampleService");
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_MTOM, Constants.VALUE_TRUE);
String clientFolder = "/home/amila/work/articles/binary/samples/mtom/client/";
DataSource dataSource = new FileDataSource(clientFolder + "client.jar");
DataHandler request = new DataHandler(dataSource);
DataHandler response = stub.echoBinaryData(request);
FileOutputStream fileOutputStream = new FileOutputStream(clientFolder + "server.jar");
response.writeTo(fileOutputStream);
fileOutputStream.flush();
System.out.println("Saved the file to client folder");
} catch (AxisFault axisFault) {
axisFault.printStackTrace();
} catch (java.rmi.RemoteException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

Skelton code.
public javax.activation.DataHandler echoBinaryData
(
javax.activation.DataHandler param
) {

String serverFolder = "/home/amila/work/articles/binary/samples/mtom/server/";
try {
FileOutputStream fileOutputStream = new FileOutputStream(serverFolder + "client.jar");
param.writeTo(fileOutputStream);
fileOutputStream.flush();
System.out.println("Saved the file to client.jar");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

FileDataSource fileDataSource = new FileDataSource(serverFolder + "server.jar");
DataHandler dataHandler = new DataHandler(fileDataSource);
return dataHandler;
}