Tuesday, 12 February 2013


Upgrade to Axis2.0 for client and server communication:

Considering existing application is based on Axis1 which you plan to upgrade to Axis2. Axis2 provide benefits in area of speed in processing and low memory footprints while serialization and deserialization.

Following are the server and client side changes required for migration to Axis 2:

Server side changes:
In order to expose ejb as web services we need to add all the jar provided with Axis2 distribution to lib folder under WEB-INF and also need to create a new folder with name services
- WEB-INF
+ lib ----------- containing Axis2 jars
- services
- MyServiceEjb
- META-INF
services.xml ----------- this contain the actual the details of exposing our services
- conf
axis2.xml

Sample services.xml for exposing MyServiceEJB
<serviceGroup>
<service name="MyService" >
<description>MyServiceEJB</description>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
class="org.apache.axis2.rpc.receivers.ejb.EJBInOnlyMessageReceiver"/>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.ejb.EJBMessageReceiver"/>
</messageReceivers>
<parameter name="ServiceClass">com.MyServiceEjbLocal</parameter>
<parameter name="localInterfaceName">com.MyServcieEjbLocal</parameter>
<parameter name="localHomeInterfaceName">com.MyServcieEjbHome</parameter>
<parameter name="beanJndiName">ejb:MyService.MyServiceEjbHome</parameter>
</service>
</serviceGroup>



And some changes required in axis2.xml which need to be placed in WEB-INF/conf folder

<parameter name="servicePath">services/MyServices</parameter>
This parameter specifies service url on which our services are exposed


Handlers


For handlers Axis2 has introduces a new concept of modules which is similar to handlers what we already have in Axis1. Now instead of extending AbstractHandler we will be using BasicHandler

Sample Example
public class AuthenticateHandler extends BasicHandler{
 @Override
 public
void invoke(MessageContext messageContext) throws AxisFault {

   //
TODO Auto-generated method stub

   System.out.println("YourLogic");
   
   messageContext.getRequestMessage().getSOAPEnvelope()
   .getHeaderByName("username","user");
 }
}
After this we need to add an entry in modules.xml which provides information of handler under WEB-INF/modules/Authenticate/modules.xml

Logging is also done through handler.

Sample modules.xml for Authentication handler
<module name="Authenticate" class="handler.AuthenticateModule">
<inflow>
<handler name="InFlowAuthenticationHandler" class="handler.AuthenticationHandler">
<order phase="AuthenticationPhase"/>
</handler>
</inflow>
<outflow>
<handler name="OutFlowAuthenticationHandler" class="handler.AuthenticationHandler">
<order phase="AuthenticationPhase"/>
</handler>
</outflow>
<Outfaultflow>
<handler name="FaultOutFlowAuthenticationHandler" class="handler.AuthenticationHandler">
<order phase="AuthenticationPhase"/>
</handler>
</Outfaultflow>
<INfaultflow>
<handler name="FaultInFlowAuthenticationHandler" class="handler.AuthenticationHandler">
<order phase="AuthenticationPhase"/>
</handler>
</INfaultflow>
</module>

Add this phase(AuthenticationPhase) in axis2.xml after that this phase is available to be engaged in service, now an entry is made in services.xml for which service we want to add this module.
<phase name="AuthenticationPhase"/> --------in axis2.xml
<module ref="Authenticate"/> --------in services.xml

· Care needs to be taken for beans because for axis2 we should have a getter if we have a setter implemented in the bean; otherwise we get a generic error message which can be mistaken for a jar conflict kind of an issue.

· Collection frame work is not supported; we need to refactor such cases.

· Overloaded methods are not supported in axis2, need to refactor such method.

· java.lang.Object is not supported in axis2, we should be transferring proper data type.


With these changes we are done with exposing our services.

List of Services:

And unlike Axis1 where we we get the list of all the services exposed automatically when we sent a request to localhost:8080/services/MyServices, we are required to handle this explicitly in our code in Axis2 by implementing a servlet which will display the list.

axisConfig = ConfigurationContextFactory.createConfigurationContextFromFileSystem(PATH_TO_WEB-INF, PATH_TO_AXIS2.XML).getAxisConfiguration();
StringBuffer output = new StringBuffer("<html><body><h1>Available Services:</h1>");
int index = 1;

for (Iterator iter = axisConfig.getServices().values().iterator(); iter.hasNext();){
AxisService service = (AxisService) iter.next();
output.append("<p>");
output.append(index).append(". ");
output.append("<a href=\"" + "Services/MyServices/" + service.getName() + "?wsdl\">");
output.append(service.getName());
output.append("</a>");
for(Iterator iter1 = service.getOperations(); iter1.hasNext();){
AxisOperation a = (AxisOperation)iter1.next();
output.append("<BR>"+a.getName());
}

output.append("<p/>");
}
output.append("</body></html>");






Client side changes

For generating client stubs axis2 supports 4 binding techniques
1. adb (axis2 data binding)
2. XmlBeans
3. Jibx
4. Jaxbri


For adb we can create stubs through wsdl2java tools provided by axis2
wsdl2java -oD:\workSpace\YourProjectClient\ -S src\ -R resource\ -d adb -uw -u -sp -or --noBuildXML --noWSDL -urihttp://localhost:8080/services/MyServices/MyServices?wsdl
Short Option
Long Option
Description
-uri <Location of WSDL>
None
WSDL file location. This should point to a WSDL file in the local file system.
-o <output Location>
--output <output Location>
Output file location. This is where the files would be copied once the code generation is done. If this option is omitted the generated files would be copied to the working directory.
-l <language>
--language <language>
Output language. Currently the code generator can generate code in Java but it has the ability to be extended to support other languages.
-p <package name>
--package <package name>
The target package name. If omitted, a default package (formed using the target namespace of the WSDL) will be used.
-a
--async
Generate code only for async style. When this option is used the generated stubs will have only the asynchronous invocation methods. Switched off by default.
-s
--sync
Generate code only for sync style . When this option is used the generated stubs will have only the synchronous invocation methods. Switched off by default. When used with the -a option, this takes precedence.
-t
--test-case
Generates a test case. In the case of Java it would be a JUnit test case.
-ss
--server-side
Generates server side code (i.e. skeletons). Default is off.
-sd
--service-description
Generates the service descriptor (i.e. server.xml). Default is off. Only valid with -ss, the server side code generation option.
-d <databinding>
--databinding-method <databinding>
Specifies the Databinding framework. Valid values are xmlbeans, adb, jibx, and none. Default is adb.
-g
--generate-all
Generates all the classes. This option is valid only with the -ss (server side code generation) option. When on, the client code (stubs) will also be generated along with the skeleton.
-u
--unpack-classes
Unpack classes. This option specifies whether to unpack the classes and generate separate classes for the databinders.
-sn <service name>
--service-name <service name>
Specifies the service name to be code generated. If the service name is not specified, then the first service will be picked.
-pn <port name>
--port-name <port name>
Specifies the port name to be code generated. If the port name is not specified, then the first port (of the selected service) will be picked.
-ns2p
--namespace2package
Specifies a comma separated list of namespaces and packages where the given package will be used in the place of the auto generated package for the relevant namespace. The list will be the format of ns1=pkg1,ns2=pkg2.
-ssi
--serverside-interface
Generate an interface for the service skeleton.
-wv
--wsdl-version
WSDL Version. Valid Options : 2, 2.0, 1.1
-S
--source-folder
Specify a directory path for generated source
-R
--resource-folder
Specify a directory path for generated resources
-em
--external-mapping
Specify an external mapping file
-f
--flatten-files
Flattens the generated files
-uw
--unwrap-params
Switch on un-wrapping
-xsdconfig
Use XMLBeans .xsdconfig file. Valid only with -d xmlbeans
-ap
--all-ports
Generate code for all ports
-or
--over-ride
Overwrite the existing classes
-b
--backword-compatible
Generate Axis 1.x backward compatible code
-sp
--suppress-prefixes
Suppress namespace prefixes (Optimization that reduces size of soap request/response)
--noBuildXML
Don't generate the build.xml in the output directory
--noWSDL
Don't generate WSDL's in the resources directory
--noMessageReceiver
Don't generate a MessageReceiver in the generated sources



The stubs generated are different from Axis1 stubs, following are the differences compared to Axis2 stubs:

1.    Unlike Axis1 we cannot control package structure for beans it will be similar to what we have on server side.

2.    The custom exception from the application will get wrapped inside another class which has its name as SERVICE_NAME+YourAppException

a.    eg. for MyException it was throwing MyServiceMyException

3.    This resulted in different exception for different services


Code on client side for axis2.
try{
MyServiceStub d = new MyServiceStub();
System.out.println(d.helloWorld());
System.out.println(d.say("hi"));
}
catch (MyServiceMyException e) {
e.printStackTrace();
e.getFaultMessage().getMyException().getErrorMsg();
}

And as XmlBeans does not support unwrapped format of stubs generation this binding technique results in putting extra efforts while invoking the service methods. For eg. “say” is a method in MyServiceStub in order to call this method we needed to instantiate this
and set parameters in this if our method requires parameter
MyServiceStub d = new MyServiceStub();
com.Say k = new Say();---------- method instantiation
k.setArgs0("hi");---------- setting parameter
System.out.println(d.say(k).get_return());---------- retrieving return type


Following is the summarized list for changes required to make the server and client axis 2 communication possible:

1.    Above mentioned steps.

2.    Refactoring all client exception handling code.


Using ADB databinding with the changes as suggested above server client communication is achieved.

Issue related to exception for which we tried different approaches.

Issues:

Different alternatives that were tried on client side in order to get the exception same as on Server side

1. As this issue was already reported in axis2 https://issues.apache.org/jira/browse/AXIS2-3262, so we implemented a work around as suggested in this jira for this we implemented a handler which intercepts every request and response and checks if exception has been thrown or not from server side. In case of exception it unwraps exception and extracts the error code and message and then throws it as MyException.

public InvocationResponse invoke(MessageContext msgContext) throws AxisFault {
if (msgContext.isFault()) {
org.apache.axiom.soap.SOAPFactory fac =
msgContext.isSOAP11() ? OMAbstractFactory.getSOAP11Factory() : OMAbstractFactory
.getSOAP12Factory();
System.out.println();
org.apache.axiom.soap.SOAPFaultCode PlaceNewLocalNameHere = msgContext.getEnvelope().getBody().getFault().getCode();
msgContext.setProperty(SOAP12Constants.SOAP_FAULT_CODE_LOCAL_NAME, PlaceNewLocalNameHere);
SOAPFaultReason PlaceNewReasonHere = msgContext.getEnvelope().getBody().getFault().getReason();
msgContext.setProperty(SOAP12Constants.SOAP_FAULT_REASON_LOCAL_NAME, PlaceNewReasonHere);
org.apache.axiom.soap.SOAPFaultDetail PlaceNewDetailHere = msgContext.getEnvelope().getBody().getFault().getDetail();
msgContext.setProperty(SOAP12Constants.SOAP_FAULT_DETAIL_LOCAL_NAME, PlaceNewDetailHere);
throw new MyException(1 , msgContext.getFailureReason().getMessage());
}
return InvocationResponse.CONTINUE;
}

And then we need to add following piece of code before instantiating stub and need to pass this configuration context to it.
ConfigurationContext c = ConfigurationContextFactory.createConfigurationContextFromFileSystem("src\\client\\FaultHandler.java", "src\\conf\\axis2.xml");
List phases_list = c.getAxisConfiguration().getInFlowPhases();
Phase phaseOne=(Phase) phases_list.get(0);
phaseOne.addHandler(new FaultHandler()); ------- this is the handler which converts exception
phases_list.add(phaseOne);
c.getAxisConfiguration().setInFaultPhases(phases_list);

MyServiceStub d = new MyServiceStub(c);

But limitation with this approach are:
a) We are still have to catch MyServiceMyException as the stubs have methods which throw this exception.
b) We need to create MyException class on client side similar to server side separately as generated stubs do not have this as exception.
c) We are not forced to handle MyException since this is not an exception being thrown from the method itself so it gets treated as runtime exception.

try{
MyServiceStub d = new MyServiceStub(c);
System.out.println(d.helloWorld());
System.out.println(d.say("hi"));
}
catch (MyServiceMyException e) { --------- we are forced to catch this

} catch (MyException e) { ----------need to create MyException class on Client side
e.printStackTrace();
}


2. We also tried contract first approach where we converted the axis1 web services from rpc-encoded to document literals style by making changes in server-config.wsdd
<service name="MyServices/MyServices" provider="java:EJB" style="document" use="literal">
<parameter name="allowedMethods" value="*"/>
<parameter name="localInterfaceName" value="com.MyServicelEjbLocal"/>
<parameter name="localHomeInterfaceName" value="com.MyServiceEjbHome"/>
<parameter name="beanJndiName" value=" ejb:MyService.MyServiceEjbHome "/>
<beanMapping languageSpecificType="java:exception.MyException" qname="common:MyException"/>
<typeMapping
qname="common:MyException"
languageSpecificType="java:exception.MyException"
serializer="org.apache.axis.encoding.ser.ArraySerializerFactory"
deserializer="org.apache.axis.encoding.ser.ArrayDeserializerFactory"/>
</service>
and created server side stubs from the generated wsdl. When we exposed our service again with Axis2 we were still getting wrapped exception.


3. We tried Jibx data binding technique which required binding.xml along with wsdl which provides details of the beans and the exception structure that we are using in our service and this will be used while creating stubs.
For this we required jibx jar and used a tool to create binding.xml provided by jibx
org.jibx.binding.generator.BindGen
D:\workSpace\testProject>java -cp "D:\jibx\lib\*";"D:\project\lib\*" org.jibx.binding.generator.BindGenexception.MyException

sample binding.xml
<binding name="binding" package="exception">
<mapping abstract="true" type-name="MyException" class="exception.MyException">
<value style="element" name="errorCode" field="errorCode" usage="optional"/>
<value style="element" name="errorMessage" field="errorMessage " usage="optional"/>
</mapping>
<mapping class="exception.MyException" name="MyException">
<structure map-as="MyException"/>
</mapping>
</binding>

And while creating stubs we will require to provide this binding.xml

wsdl2java -o D:\workSpace\testProjectClient\ -S src\ -R resource\ -d jibx -Ebindingfile D:\binding.xml-uw -u -sp -or --noBuildXML --noWSDL -uri http://localhost:8080/services/MyServices /MyService?wsdl

With this binding technique also we were facing same wrapped exception issue.

4. We also tried one more suggested approach with ADB data binding which required changes to be made in axis2-adb-codegen.jar which is provided with other axis2 jar.In this approach we edited schema-compiler.properties file which determines bean generation template.
(A bean writer provider
# may choose not to use a template at all!) However the property loader will
# load the following templates (reference) and provide it and in the case when the
# bean writer is not statically bound to a template, this'll be useful.
#
schema.bean.writer.template=/org/apache/axis2/schema/template/ADBBeanTemplate.xsl
# schema.bean.writer.template=/org/apache/axis2/schema/template/PlainBeanTemplate.xsl
# The type map to be used by the schema compiler
#
schema.bean.typemap=org.apache.axis2.schema.typemap.JavaTypeMap

But this thing resulted in only plain bean instead of ADB bean.


Exception handling mechanism of Axis2 we will require handling exception separately for every service.