Web Services With Axis2

From MidrangeWiki
Revision as of 02:00, 3 April 2008 by Garethu (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Introduction

We will be using Ant, Axis2 and Tomcat for this exercise. Tomcat is the server, and will host Axis2, which in turn is the application that will host the web service. Ant is a build system we will use to automate repetitive tasks for our web service such as copying files, compiling source and creating jar files.
We also assume that all of the software is loaded and setup correctly. Please refer to the following websites if assistance is needed :
Setup Apache Tomcat
Installation Guide for Axis2
Installing Ant
First we will create a RPG program which will hold all the logic for the required web service. Then, we will create the Java web service and client to access the RPG program.
Axis2 has 3 possible supported data bindings : Axis Data Binding (ADB), XMLBeans, JibX. We will be using ADB.
When discussing the build file for Ant, please ensure that all the property names AXIS2_HOME, AXIS2_SERVICES_HOME and CATALINA_HOME_BIN match those of your environment.

AXIS2_HOME will be the library (lib) for Axis2.

Ensure that you have the following code set up for the Tomcat connection pooling to work in context.xml file at D:\Tools\apache-tomcat-6.0.14\webapps\axis2\META-INF :
<Context reloadable="true" cachingAllowed="false">
	  
	<Resource name="jdbc/dev400" auth="Container" type="javax.sql.DataSource" 
                  maxActive="100" maxIdle="10" maxWait="1000" removeAbandoned="true"  removeAbandonedTimeout="60"
                  username="guren001" password="password" driverClassName="com.ibm.as400.access.AS400JDBCDriver" 
                  url="jdbc:as400://<ip address>/;libraries=$GULIB;naming=system;errors=full;date format=iso" /> 
                  
</Context>
Also check your web.xml file at D:\Tools\apache-tomcat-6.0.14\webapps\axis2\WEB-INF, and make sure you have the following entry :
<resource-ref>
  <description>DB Connection</description>
  <res-ref-name>jdbc/dev400</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>
Please email me if you experience any problems or have any additional questions regarding this tutorial.

RPG Backend

Create the following program in your library of choice eg. library $GULIB, program WSCALC.
NB : Please replace $GULIB with your library throughout the documentation
H DftActGrp(*No)
                                                      
D userId          s             10                    
D operation       s              3                    
D number1         s             10i 0                 
D number2         s             10i 0                 
D result          s             10i 0                 
D response        s            100                    
                                                      
C     *entry        plist                             
C                   parm                    userId    
C                   parm                    operation 
C                   parm                    number1   
C                   parm                    number2   
C                   parm                    response  
                                                      
 /free                                                
  // Addition                                         
  if operation = 'ADD';                               
    result = number1 + number2;                                      
                                                                     
 // Subtraction                                                      
 elseif operation = 'SUB';                                           
    result = number1 - number2;                                      
                                                                     
 // Invalid operation                                                
 else;                                                               
    result = 0;                                                      
 endif;                                                              
                                                                     
 response  = 'Response from Gareth : '                               
           +  %trim(userId) + ' , the result = ' + %char(result);    
                                                                     
 *inlr = *on;                                                        
/end-free                                                            
Compile the program
CRTBNDRPG PGM(WSCALC)   
Once the program has been compiled succesfully, go to the command line and issue the following command :
STRSQL 
On the "Enter SQL Statements" screen, issue the following command :
CREATE PROCEDURE $GULIB/WSCALC(INOUT userid CHAR (10 ), INOUT       
operation CHAR (3 ), INOUT number1 INT , INOUT number2 INT , INOUT  
response CHAR (100 )) LANGUAGE RPGLE NOT DETERMINISTIC NO SQL       
EXTERNAL NAME $GULIB/WSCALC PARAMETER STYLE GENERAL                 
This command creates stored procedure for the program WSCALC you created previously.
  • Sidebar
A stored procedure is a host program that attaches itself to the database and is supported by JDBC 
through the CallableStatement class. This allows the Java program to call AS/400 programs, 
in our case, WSCALC.

Java Backend

Ensure you have the jt400.jar file available in your repository.
Open your Java editor of choice and name your project :
CalculatorPrj
Create a package
za.co.webservices.calculator.service
Under this package, create a class
CalculatorService

Enter the following code :

package za.co.webservices.calculator.service;

import za.co.webservices.calculator.bus.WebService;

public class CalculatorService {

	public String calculator (String userId, String operation, int number1, int number2) {
		WebService accessISeries = new WebService();
		String response = accessISeries.callProgram(userId, operation, number1, number2);
		return response;
	} 
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		CalculatorService cs = new CalculatorService();
		String response = cs.calculator("Testing", "ADD", 1, 2);
		System.out.println("The reponse : " + response);
		System.exit(0);
	}
	
}


There will be errors in your code, as Java cannot find the classes it attempts to load. Now we'll create the needed code.
Under the "src" directory create a package
za.co.webservices.calculator.bus
Under this package, create a class
WebService
Enter the following code :
package za.co.webservices.calculator.bus;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class WebService {

	/** The handle to the AS/400 program */
	protected CallableStatement rpgCalculator;

	/** The connection to the database retrieved from a connection pool */
	protected Connection conn;

	/**
	 * @param userId
	 * @param operation
	 * @param number1
	 * @param number2
	 * @return
	 */
	public String callProgram(String userId, String operation, int number1,	int number2) {

		// initialise field for the response parameter
		String response = null;

		// get a database connection from the pool - used in a server
		// environment
		// conn = getDataBaseConnection();

		// get a database connection on the fly - used in a stand-alone
		// environment
		conn = getConnnectionForTest();

		// setup the callable statement
		try {
			System.out.println("setting up parameters");
			rpgCalculator = conn.prepareCall("CALL WSCALC (?,?,?,?,?)");
			rpgCalculator.registerOutParameter(1, java.sql.Types.CHAR);
			rpgCalculator.registerOutParameter(2, java.sql.Types.CHAR);
			rpgCalculator.registerOutParameter(3, java.sql.Types.INTEGER);
			rpgCalculator.registerOutParameter(4, java.sql.Types.INTEGER);
			rpgCalculator.registerOutParameter(5, java.sql.Types.CHAR);

			rpgCalculator.setString(1, userId);
			rpgCalculator.setString(2, operation);
			rpgCalculator.setInt(3, number1);
			rpgCalculator.setInt(4, number2);
			rpgCalculator.setString(5, "");

			// call the RPG program
			System.out.println("call the program");
			rpgCalculator.executeUpdate();

			response = rpgCalculator.getString(5);

		} catch (SQLException e) {
			System.out.println("SQLException while calling the RPG Program : "
					+ e.getMessage());
			response = e.getMessage();
			e.printStackTrace();
		} finally {
			if (rpgCalculator != null) {
				try {
					rpgCalculator.close();
				} catch (SQLException e) {
				}
				rpgCalculator = null;
			}
		}

		return response;
	}

	public Connection getDataBaseConnection() {
		Context initCtx = null;
		Context envCtx = null;
		DataSource ds = null;
		Connection conn = null;

		try {
			initCtx = new InitialContext();
		} catch (NamingException e) {
			System.out.println("InitContext Exception : " + e.getMessage());
			e.printStackTrace();
		}

		try {
			envCtx = (Context) initCtx.lookup("java:comp/env");
		} catch (NamingException e) {
			System.out.println("envContext Exception : " + e.getMessage());
			e.printStackTrace();
		}

		try {
			ds = (DataSource) envCtx.lookup("jdbc/dev400");
		} catch (NamingException e) {
			System.out.println("DataSource Exception : " + e.getMessage());
			e.printStackTrace();
		}

		try {
			conn = ds.getConnection();
		} catch (SQLException e) {
			System.out.println("SQLException while trying to get connection : "
					+ e.getMessage());
			e.printStackTrace();
		}

		return conn;
	}

	public Connection getConnnectionForTest() {

		try {
			Class.forName("com.ibm.as400.access.AS400JDBCDriver");
			Connection conn = DriverManager.getConnection("jdbc:as400://<your ip address>/;libraries=$GULIB;naming=system;errors=full;date format=iso", "GUREN001", "PASSWORD");
			return conn;
		} catch (Exception e) {
			System.out.println("Error occured while getting connection...");
			e.printStackTrace();
			return null;
		}
	}
}
You will receive errors when attempting to compile your program. This is due to the fact that you do not have the library for the AS/400 in your classpath.
Add the jt400.jar file to your classpath. You should have no errors at this stage.
Compile and Run the application.
On the console, you should have the following output :
setting up parameters
call the program
The reponse : Response from Gareth Uren: Testing , the result = 3                                                 
  • Sidebar
You would have noticed in the code there 2 methods to get the connection to the AS/400. 
One method is for stand alone environments, such as the results we got when we ran the program.
This used the method getConnnectionForTest(). The other method, getDataBaseConnection(), 
will be used in our server environemnt, more specifically, by Tomcat.
Tomcat will manage the connections to the AS/400 for us and will use the DBCP software to aid it. 
For our tutorial, we put all of the code into the one class for brevity and clarity, and
it is just a matter of commenting out the one connection we don't need and this will suffice. 
However, this is not the preferred way of coding the Java program. A better option would be to put 
the 2 methods in a "connection" package and use dependency injection eg. Spring. This will eliminate 
hard coding which options we want to use in the program.
We need to inform Axis2 that we are going to install this code as a web service. We will do this with the services file. Do the following:
Create a "resources" folder under the "CalculatorPrj" folder. 
Now create a "META-INF" folder under the "resources" folder.
You should have the following structure : D:\Apps\WebServicesWorkspace\CalculatorPrj\resources\META-INF
Create a file called services.xml in this folder.
The file should now be accessible in the following directory as : D:\Apps\WebServicesWorkspace\CalculatorPrj\resources\META-INF\services.xml

Enter the following in the services.xml file  :

<service name="CalculatorService" scope="application" targetNamespace="http://calculator.webservices.co.za/">
    <description>
        Calculator Service
    </description>
    <messageReceivers>
        <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
                         class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
        <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
                         class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </messageReceivers>
    <schema schemaNamespace="http://calculator.webservices.co.za/xsd"/>
    <parameter name="ServiceClass">za.co.webservices.calculator.service.CalculatorService</parameter>
</service>
We need an Ant build file. It will accomplish the following in PHASE 1:
1. Compile the source code.
2. Generate a WSDL file.
3. Create the aar file which needs to be deployed to Axis2 server.
4. Copy the aar file to the Axis2 Server.
5. Create the client stub, which will create skeleton code for testing the web service from our IDE.
Create a build.xml file under the "CalculatorPrj" and copy the following code into it. It will execute the 5 steps as part of PHASE 1.
<project name="CalculatorPrj" default="client.init" basedir=".">

	<!-- properties -->
	<property file="${env}"/>
	<property name="project.base.dir" value="."/>
	<property name="client.base.dir" value="${project.base.dir}/build"/>
	<property name="src" value="${project.base.dir}/src"/>
	<property name="resources" value="${project.base.dir}/resources"/>
	<property name="AXIS2_HOME" value="D:\Tools\axis2-1.3"/>
	<property name="AXIS2_SERVICES_HOME" value="D:\Tools\apache-tomcat-6.0.14\webapps\axis2\WEB-INF\services"/>
	<property name="CATALINA_HOME_BIN" value="D:\Tools\apache-tomcat-6.0.14\bin"/>
	<property name="build.dir" value="build"/>
	<property name="lib" value="${build.dir}/lib"/>
	
	<property name="client.build" value="${client.base.dir}/build"/>
	<property name="client.classes" value="${client.build}/classes"/>
	<property name="client.lib" value="${client.build}/lib"/>
	<property name="client.src" value="${client.base.dir}/src"/>
	
	 <property value="" name="jars.ok"/>
	 <property name="name" value="CalculatorService"/>
	
	<!-- classpath -->
	<path id="full.classpath">
		<fileset dir="${AXIS2_HOME}/lib">
			<include name="*.jar"/>
		</fileset>
	</path>
		
	<target name="compile.service">
		<mkdir dir="${build.dir}"/>
		<mkdir dir="${build.dir}/classes"/>
		<javac debug="on" fork="true" destdir="${build.dir}/classes" srcdir="${basedir}/src" classpathref="full.classpath"/>
        	</target>
	
	<!-- create the wsdl file -->
	<target name="generate.wsdl" depends="compile.service">
		<echo message="Generate the WSDL file"/>
		<taskdef name="java2wsdl"
			classname="org.apache.ws.java2wsdl.Java2WSDLTask"
			classpathref="full.classpath"/>
		<java2wsdl className="za.co.webservices.calculator.service.TradeQuestService"
			outputLocation="${build.dir}"
			targetNamespace="http://calculator.webservices.co.za/"
			schemaTargetNamespace="http://calculator.webservices.co.za/xsd">
			<classpath>
				<pathelement path="${axis2.classpath}"/>
				<pathelement location="${build.dir}/classes"/>
			</classpath>
		</java2wsdl>
		<echo message="Generation of the WSDL file complete"/>
	</target>
	
	<!-- create the aar file to deploy on axis2 server. The generate.service does not really depend on 
	       generate.wsdl but rather on compile.service. However, generate.wsdl already compiles the source and 
	       this is therefore just for sequencing the targets	
	-->
	<target name="generate.service" depends="generate.wsdl">
		<!--aar them up, Scotty -->
		<copy toDir="${build.dir}/classes" failonerror="false">
			<fileset dir="${basedir}/resources">
				<include name="**/*.xml"/>
			</fileset>
		</copy>
		<jar destfile="${build.dir}/CalculatorService.aar">
			<fileset excludes="**/Test.class" dir="${build.dir}/classes"/>
		</jar>
	</target>
	
	<!-- copy the aar file to the server -->
	<target name="copy.to.service" depends="generate.service">
		<echo message="Copying aar file to Axis2 Services"/>
		<copy todir="${AXIS2_SERVICES_HOME}">
			<fileset dir="${build.dir}">
				<include name="**/*.aar"/>
			</fileset>	
		</copy>         
		<echo message="Completed copying aar file to Axis2 Services"/>
	</target>
		
	<!-- 	create the client stub -->
	<target name="generate.stub" depends="copy.to.service">
		<echo message="Generate the client stub"/>
		<exec dir="${client.base.dir}" executable="cmd" >
			<arg line="/c %AXIS2_HOME%\bin\wsdl2java -uri CalculatorService.wsdl -p za.co.webservices.calculator.client -d adb -s"/>
		</exec>
		<echo message="Generation of the client stub complete"/>
	</target>
		
	<target name="client.init" depends="generate.stub">
		<mkdir dir="${client.base.dir}/build"/>
		<mkdir dir="${client.classes}"/>
		<mkdir dir="${client.lib}"/>
		
		<!-- copy a test client to the client src directory -->
		<echo message="Copying sample test client from ant workspace client source directory."/>
		<copy todir="${client.src}/za/co/webservices/calculator/client">
			<fileset dir="${project.base.dir}">
				<include name="Client.java"/>
			</fileset>	
		</copy>         
		<echo message="Copying sample test client from ant workspace client source directory complete."/>
	</target>
	
	<target name="pre.compile.test"  >
		<!--Test the classpath for the availability of necesary classes-->
		<available classpathref="full.classpath" property="stax.available" classname="javax.xml.stream.XMLStreamReader"/>
		<available classpathref="full.classpath" property="axis2.available" classname="org.apache.axis2.engine.AxisEngine"/>
		<condition property="jars.ok"> 
			<and> 
				<isset property="stax.available"/> 
				<isset property="axis2.available"/>
			</and>
		</condition>
		<!--Print out the availabilities-->
		<echo message="Stax Availability= ${stax.available}"/>
		<echo message="Axis2 Availability= ${axis2.available}"/>
	</target>
    
	<target name="compile.src" depends="pre.compile.test" if="jars.ok">
		<javac debug="on" memoryMaximumSize="256m" memoryInitialSize="256m" fork="true" destdir="${client.classes}" srcdir="${client.src}">
			<classpath refid="full.classpath"/>
		</javac>
	</target>
	
	<target if="jars.ok" name="jar.client" depends="compile.src">
		<jar destfile="${client.lib}/${name}-test-client.jar">
			<fileset dir="${client.classes}">
				<exclude name="**/META-INF/*.*"/>
				<exclude name="**/lib/*.*"/>
				<exclude name="**/*MessageReceiver.class"/>
				<exclude name="**/*Skeleton.class"/>
			</fileset>
		</jar>
		
		<!-- copy the the jar to nested directory -->
		<echo message="Copying lib/jar to classes/lib."/>
		<mkdir dir="${client.classes}/lib"/>
		<copy todir="${client.classes}/lib">
			<fileset dir="${client.lib}">
				<include name="${name}-test-client.jar"/>
			</fileset>	
		</copy>         
		
		<!-- copy the the test.xml to nested directory -->
		<echo message="Copying test.xml for ant to classes."/>
		<copy todir="${client.classes}">
			<fileset dir="${project.base.dir}">
				<include name="test.xml"/>
			</fileset>	
		</copy>  
		
		<!-- Start Tomcat -->
		<echo message="Starting tomcat"/>
		<exec dir="${CATALINA_HOME_BIN}" executable="cmd" >
			<arg line="/c startup"/>
		</exec>
	</target>
	
	<target name="clean">
		<delete dir="${project.base.dir}/build"/>
		<delete dir="${AXIS2_SERVICES_HOME}">
			<include name="CalculatorService.aar"/>
		</delete>	
		<echo message="Stopping tomcat"/>
		<exec dir="${CATALINA_HOME_BIN}" executable="cmd" >
			<arg line="/c shutdown"/>
		</exec>
	</target>
</project>
Now that we have a build file, we can complete PHASE 1

Go the dos prompt and type in the following :

D:\Apps\WebServicesWorkspace\CalculatorPrj\ant

Post run investigation:

All our source is compiled. The classes are found in the D:\Apps\WebServicesWorkspace\CalculatorPrj\build\classes\za\co\webservices\calculator directory.
In the following directory, D:\Apps\WebServicesWorkspace\CalculatorPrj\build\src\za\co\webservices\calculator\client, the client stub is created for us.
The aar file has been created and copied to the Axis2 server, ready to be run as a service. The file is copied to D:\Tools\apache-tomcat-6.0.14\webapps\axis2\WEB-INF\services.
The WSDL file will be found at D:\Apps\WebServicesWorkspace\CalculatorPrj\build.
After investigating the client stub, CalculatorServiceStub, we observe the following :
 1. In the default constructor, we have the endpoint to our web service.
 2. Look for the following line : ''public static class <YourClassName> implements org.apache.axis2.databinding.ADBBean''. 
    This will be the object used to pass the parameters to the RPG program. In our example, <YourClassName> will be Calculator.
 3. Now we have to look through the code for all the setters of the parameters. We know that we have CalculatorService program,
    that we have 4 parameters we are passing.
    So now we look for the following setUserId, setOperation, setNumber1, setNumber2. We will find these methods in the stub. 
 4. We also need to hold the reponse data from the web service. 
    Look for the following line : ''public static class <ResponseObject> implements org.apache.axis2.databinding.ADBBean''.
    This will be the object used to store the data retrieved from the RGP program. In our example, <ResponseObject> will be CalculatorResponse.
 5. The following line : ''public za.co.webservices.calculator.client.CalculatorServiceStub.CalculatorResponse calculator''
     tells us what method to call from our stub. It will be ''calculator''.
With the knowledge of our endpoint, the object which we are going to use to pass in parameters and the actual parameters, we can now build the Client code.
The following code should be in the directory : D:\Apps\WebServicesWorkspace\CalculatorPrj\build\src\za\co\webservices\calculator\client
package za.co.webservices.calculator.client;

import za.co.webservices.calculator.client.CalculatorServiceStub.*;

public class Client{
    public static void main(java.lang.String args[]){
        try{
            CalculatorServiceStub stub = new CalculatorServiceStub ("http://localhost:8080/axis2/services/CalculatorService");
            genericMethod(stub);
        } catch(Exception e){
            e.printStackTrace();
            System.out.println("\n\n\n");
        }
    }

    /* do in only */
    public static void genericMethod(CalculatorServiceStub stub){
        try{
        	
        	Calculator req = new Calculator();
        	req.setUserId("Testing");
        	req.setOperation("ADD");
        	req.setNumber1(1);
        	req.setNumber2(2);

            CalculatorResponse res = stub.calculator(req);
            System.out.println(res.get_return());	
        } catch(Exception e){
        	System.out.println("\n\n");
            e.printStackTrace();
            System.out.println("\n\n");
        }
    }
}
We are now ready for PHASE 2.
We also need to create an Ant file which will run the test script. The test script will assume that a jar file will have been pre-built already. This will be built in our next step. Create a file called test.xml in directory D:\Apps\WebServicesWorkspace\CalculatorPrj :
<project default="run">
	<property name="AXIS2_HOME" value="D:\Tools\axis2-1.3"/>
	<property name="lib-dir" location="lib" />

	<path id="axis2.classpath">
		<fileset dir="${AXIS2_HOME}/lib">
			<include name="*.jar"/>
		</fileset>
	</path>

	<target name="run"> 
		<java classname="za.co.webservices.calculator.client.Client"  classpathref="axis2.classpath">
			<classpath>	
				<pathelement location="${lib-dir}/CalculatorService-test-client.jar"/>
			</classpath>	
		</java>
	</target>
</project>
We now need to create a jar file which will create the software needed to test the web service, and which test.xml will use.
Run the ant file, build.xml, with the target set to client.jar from directory D:\Apps\WebServicesWorkspace\CalculatorPrj :
ant client.jar
A jar file, CalculatorService-test-client, will have been created in directory D:\Apps\WebServicesWorkspace\CalculatorPrj\build\build\lib.
The Tomcat server would have been started, and accessible from a web browser at http://localhost:8080
Navigate to the D:\Apps\WebServicesWorkspace\CalculatorPrj\build\build\classes directory and run the ant file call test.xml :
  ant -f test.xml
The results from the console should match what we got earlier on when doing a stand-alone test :
setting up parameters
call the program
The reponse : Response from Gareth Uren: Testing , the result = 3                                                 
Now that we know everything works, we can copy the Client.java file from D:\Apps\WebServicesWorkspace\CalculatorPrj\build\src\za\co\webservices\calculator\client

and place in under D:\Apps\WebServicesWorkspace\CalculatorPrj. This means that the Client.java is going to be used as the base for our tests and we will only change this code if we want to do various tests.

The first time we had to follow the following steps :
 1. PHASE 1 - to set up the environment which included compiling source code, copying file to proper directories 
    within the workspace and copying files to the server.
    We used the command : ant which in fact had a default target on client.init.
 2. PHASE 2 - to create the client code in order to ensure that the web service was working.
    We used the command : ant jar.client.
This, in effect, meant we had to run the build.xml file twice.
At this stage, as we have our environment set up correctly, we can combine the two commands by runing the following

command on our build.xml file in D:\Apps\WebServicesWorkspace\CalculatorPrj :

ant clean, client.init, jar.client
Then you can repeat the testing of the new code as explained above.