Wednesday, April 18, 2007

Webservice client using dynamic proxy

In one of my previous posts, i explained how to create a WebService and access it from a simple client. If you look at the client code which accesses the webservice, you will find that the client is using the stubs that were created at deploy time. Such clients which use "static stubs" for accessing webservices are known as "Static Stub Clients". In such approach, the clients will have to have the stubs in their classpath.

Another approach of accessing the webservices is through Dynamic Proxies where the proxies are generated at runtime. I will be showing a simple client which uses dynamic proxies to access an webservice. I wont be explaining the steps to deploy the webservice (which i have already done in my other post). This example assumes that the webservice is already deployed on the server.

The client code will be accessing the HelloWorld service and invoking the sayHelloTo method on that service (Refer my other post to see how the service has been deployed onto the server)

Here's the code for the client:


package org.myapp.service.client;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.ServiceFactory;

import org.myapp.service.HelloWorldService;

/**
* Simple webservice client which uses dynamic proxy
*
* @author Jaikiran Pai
*/
public class DynamicProxyClient {

public static void main(String args[]) {

try {

// ServiceFactory instance
ServiceFactory serviceFactory = ServiceFactory.newInstance();
System.out.println("Got the service factory");
String wsdlURLString = "http://localhost:8080/axis/services/HelloWorld?wsdl";
URL wsdlURL = new URL(wsdlURLString);
String serviceName = "HelloWorldServiceService";
String nameSpaceURI = "http://jaikiran.com";
String portName = "HelloWorld";
// get hold of the service by passing the URL of the wsdl and the
// service name
Service service = serviceFactory.createService(wsdlURL, new QName(
nameSpaceURI, serviceName));
System.out.println("Got the service: " + service);
// create the service proxy
HelloWorldService serviceProxy = (HelloWorldService) service
.getPort(new QName(nameSpaceURI, portName),
HelloWorldService.class);
System.out.println("Got the service proxy: " + serviceProxy);
System.out.println("Invoking method on service proxy........");
System.out.println(serviceProxy.sayHelloTo("Dynamic user"));

} catch (ServiceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}



1) Our first step would be to create a ServiceFactory instance, using which we can get access to a deployed service.


ServiceFactory serviceFactory = ServiceFactory.newInstance();


2) Once the ServiceFactory has been created, we will be getting hold of the service which is deployed on the server. To do this, we require
a) The url of the wsdl of the deployed service. In this example it will be http://localhost:8080/axis/services/HelloWorld?wsdl

b) The namespace uri. In this example it will be http://jaikiran.com (You can get this uri from the wsdl file)

c) The name of the service which you want to access. In this example it will be HelloWorldServiceService



String wsdlURLString = "http://localhost:8080/axis/services/HelloWorld?wsdl";
URL wsdlURL = new URL(wsdlURLString);
String serviceName = "HelloWorldServiceService";
String nameSpaceURI = "http://jaikiran.com";

// get hold of the service by passing the URL of the wsdl and the
// service name
Service service = serviceFactory.createService(wsdlURL, new QName(
nameSpaceURI, serviceName));



3) At this point you have got access to the service. But in order to invoke an method on the service, you will have to create a proxy (dynamic proxy) from this service. The Service class, has an API getPort, which creates the proxy. In order to create the proxy, we will have to pass the portName of the service (again, this can be obtained in the wsdl file), the namespace uri and the class type of the service. In this example the service interface is org.myapp.service.HelloWorldService, so we will be passing this as the class as follows:


String portName = "HelloWorld";
// create the service proxy
HelloWorldService serviceProxy = (HelloWorldService) service
.getPort(new QName(nameSpaceURI, portName),
org.myapp.service.HelloWorldService.class);


4) At this point we have got the service proxy which is of type org.myapp.service.HelloWorldService. We can now invoke our sayHelloTo method on this proxy:


System.out.println(serviceProxy.sayHelloTo("Dynamic user"));


Simple, isnt it? You have successfully invoked the webservice through a standalone client using dynamic proxy.

Thursday, January 04, 2007

How to read a properties file in a web application

The code to do this is pretty simple. But going by the number of people who keep asking this question, i thought i would post the code over here. Let's consider that you have a war file named SampleApp.war which has a properties file named myApp.properties at it's root :



SampleApp.war
|
|-------- myApp.properties
|
|-------- WEB-INF
|
|---- classes
|
|----- org
|------ myApp
|------- MyPropertiesReader.class





Let's assume that you want to read the property named "abc" present in the properties file:

----------------
myApp.properties:
----------------

abc=some value
xyz=some other value


Let's consider that the class org.myApp.MyPropertiesReader present in your application wants to read the property. Here's the code for the same:



package org.myapp;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* Simple class meant to read a properties file
*
* @author Jaikiran Pai
*
*/
public class MyPropertiesReader {

/**
* Default Constructor
*
*/
public MyPropertiesReader() {

}

/**
* Some Method
*
* @throws IOException
*
*/
public void doSomeOperation() throws IOException {
// Get the inputStream
InputStream inputStream = this.getClass().getClassLoader()
.getResourceAsStream("myApp.properties");

Properties properties = new Properties();

System.out.println("InputStream is: " + inputStream);

// load the inputStream using the Properties
properties.load(inputStream);
// get the value of the property
String propValue = properties.getProperty("abc");

System.out.println("Property value is: " + propValue);
}

}




Pretty straight-forward. Now suppose the properties file is not at the root of the application, but inside a folder (let's name it config) in the web application, something like:




SampleApp.war
|
|-------- config
| |------- myApp.properties
|
|
|-------- WEB-INF
|
|---- classes
|
|----- org
|------ myApp
|------- MyPropertiesReader.class




There will just be one line change in the above code:




public void doSomeOperation() throws IOException {
//Get the inputStream-->This time we have specified the folder name too.
InputStream inputStream = this.getClass().getClassLoader()
.getResourceAsStream("config/myApp.properties");

Properties properties = new Properties();

System.out.println("InputStream is: " + inputStream);

//load the inputStream using the Properties
properties.load(inputStream);
//get the value of the property
String propValue = properties.getProperty("abc");

System.out.println("Property value is: " + propValue);
}



That's all that is required to get it working.

Sunday, December 10, 2006

Simple Webservice on JBoss using Axis

Download the latest stable version of Axis from Axis Releases

1) Deploying Axis web application on JBoss:

I used Axis 1.4 on JBoss-4.0.4 GCA. Assuming you downloaded Axis to D: , copy the "axis" folder present in D:\Axis-1.4\webapps folder to %JBOSS_HOME%\server\default\deploy folder. The "axis" folder that you copied is nothing but an web application (containing of some servlets and jsps provided by Axis). In JBoss for a web application to be deployed, it has to be named *.war, so rename the "axis" folder in %JBOSS_HOME%\server\default\deploy to axis.war. At this point, the axis web application is ready to be deployed on JBoss. But before starting JBoss, delete the commons-logging-x.x.x.jar and log4j-x.x.x.jar present in %JBOSS_HOME%\server\default\deploy\axis.war\WEB-INF\lib directory, since JBoss maintains its own versions of these jars and packaging these jars in your application will throw exceptions of JBoss startup. After you have deleted these jars, start the JBoss server using the run.bat present in %JBOSS_HOME%\bin folder.


2) Verify Axis web application is deployed and running:

After JBoss has started successfully, go to the following URL to check whether axis is deployed properly:

http://localhost:8080/axis

If Axis was deployed successfully, you will see a welcome page which will have a few links, one of them named "List" which can be used to view the already deployed webservices. Axis by default comes with a few sample webservices which you can see on clicking the "List" link.


3) Writing our own webservice:

3a - The Java classes/interfaces:

Lets create a simple webservice which will echo a message to the user who passes his name. We will have an interface named HelloWorldService:


package org.myapp.service;

public interface HelloWorldService {

public String sayHelloTo(String userName);

}


And here's the implementing class:


package org.myapp.service;

public class HelloWorldServiceImpl implements HelloWorldService {

public String sayHelloTo(String userName) {

//message
String hello = "Hello " + userName + ". You are being watched";

System.out.println(hello);

//return the message
return hello;
}

}



Do you see anything special in this class or the interface? No, you wont. So how do you convert these into webservices? Read on...


3b - Create a wsdl file:

Now that we have the interface and the class ready, lets create a wsdl file out of these java classes. Axis comes with utilities which allow you to create the wsdl file from a java file. I wrote a small ant target which will do this for me:


<!-- Sets classpath for build -->
<path id="classpath">
<pathelement path="${java.class.path}" />
<fileset dir="${lib}">
<include name="**/*.jar" />
</fileset>
<pathelement path="${classes.dir}" />
</path>

<taskdef name="axis-java2wsdl" classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask" >
<classpath refid="classpath"/>
</taskdef>

<target name="generateWSDL" description="Generates wsdl files from the java service interfaces">
<mkdir dir="${wsdl.dir}"/>
<axis-java2wsdl classpathref="classpath"
output="${wsdl.dir}/HelloWorld.wsdl"
location="http://localhost:8080/axis/services/HelloWorld"
namespace="http://jaikiran.com"
classname="org.myapp.service.HelloWorldService">

<mapping namespace="http://jaikiran.com" package="org.myapp.service"/>

</axis-java2wsdl>
</target>


Note: You can find the entire build.xml, that i used, at the end of this article.

You will require the org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask class which is provided by axis to be in the classpath of Ant. This class comes bundled in the axis-ant.jar which i have included in the classpath of this task using the classpathref.

In this target you specify parameters like:
location - this is where the webservice will be deployed.
namespace - the namespace for your webservice
classname - the fully qualified name of the interface which you wrote in step 3a, above.
and also a mapping between the webservice namespace and your application packages.

This target will generate the wsdl file named HelloWorld.wsdl in the directory which you specified in the 'output' parameter of the target.

3c - Create the deploy.wsdd, undeploy.wsdd and the stubs:

Now that you have created a wsdl for your webservice, lets go ahead and create the deploy.wsdd (used for deploying a webservice on the server), undeploy.wsdd (used for undeploying a webservice from the server) and the required stubs for invoking the webservice from a java client.
Again, Axis provides utilities for creating these files. Axis accepts the wsdl file, which we created in step 3b as an input to create these files. Here's the Ant target which does this for us:


<taskdef name="axis-wsdl2java" classname="org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask" >
<classpath refid="classpath"/>
</taskdef>

<target name="generateWSDD" description="Generates wsdd files from the wsdl files">
<mkdir dir="${wsdd.dir}"/>
<axis-wsdl2java
output="${wsdd.dir}"
deployscope="Application"
serverside="true"
url="${wsdl.dir}\HelloWorld.wsdl">
</axis-wsdl2java>
</target>


This single target creates the deploy.wsdd, undeploy.wsdd and the stubs. As already mentioned, this target takes the wsdl file as an input which we have specified in the 'url' parameter of the axis-wsdl2java target. The files will be created in the directory mentioned in the 'output' parameter of this target. The deploy.wsdd file that gets generated will contain something like:


<!-- Use this file to deploy some handlers/chains and services -->
<!-- Two ways to do this: -->
<!-- java org.apache.axis.client.AdminClient deploy.wsdd -->
<!-- after the axis server is running -->
<!-- or -->
<!-- java org.apache.axis.utils.Admin client|server deploy.wsdd -->
<!-- from the same directory that the Axis engine runs -->

<deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

<!-- Services from HelloWorldServiceService WSDL service -->

<service name="HelloWorld" provider="java:RPC" style="rpc" use="encoded">
<parameter name="wsdlTargetNamespace" value="http://jaikiran.com"/>
<parameter name="wsdlServiceElement" value="HelloWorldServiceService"/>
<parameter name="wsdlServicePort" value="HelloWorld"/>
<parameter name="className" value="com.jaikiran.HelloWorldSoapBindingImpl"/>
<parameter name="wsdlPortType" value="HelloWorldService"/>
<parameter name="typeMappingVersion" value="1.2"/>
<operation name="sayHelloTo" qname="operNS:sayHelloTo" xmlns:operNS="http://jaikiran.com" returnQName="sayHelloToReturn" returnType="rtns:string" xmlns:rtns="http://www.w3.org/2001/XMLSchema" soapAction="" >
<parameter qname="in0" type="tns:string" xmlns:tns="http://www.w3.org/2001/XMLSchema"/>
</operation>
<parameter name="allowedMethods" value="sayHelloTo"/>
<parameter name="scope" value="Application"/>

</service>
</deployment>


You might notice that during all these steps we never mentioned our implementing class (HelloWorldServiceImpl) in any of the targets. As you can see above in the deploy.wsdd, Axis has created its own implementing class for the HelloWorldService and provided default implementations in that class. In the deploy.wsdd file, it mentioned the implementation class as follows:

<parameter name="className" value="com.jaikiran.HelloWorldSoapBindingImpl"/>

The reason why Axis created this implementing class is that it never knew that we had created our own implementation. The input that we provided to Axis for generating the wsdd files was a wsdl file through which Axis could never have known that we already have a implementation class.

Our next step would be to modify the deploy.wsdd file to mention our implementation class, in place of Axis'. So let's change the 'className' parameter to:

<parameter name="className" value="org.myapp.service.HelloWorldServiceImpl"/>


3d - Deploy the webservice onto the server:

Now we are almost ready to deploy the webservice onto the server. But before doing that, we will have to make the HelloWorldService.class and HelloWorldServiceImpl.class available in the server's classpath so that when we use this webservice, the server wont throw ClassNotFoundException. So let's compile these 2 classes and place the class files in the %JBOSS_HOME%\server\default\deploy\axis.war\WEB-INF\classes folder. Here's the Ant target that i used to compile these classes:


<target name="compile" description="Compiles java source code">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" classpathref="classpath" fork="true" srcdir="${compile.src}" excludes="org/myapp/service/client/**/*.java"/>
</target>


Once this is done, we are ready to deploy the webservice onto the server. Axis again provides a utility to do this. Here's the target for deploying the service onto the server. This target just invokes the org.apache.axis.client.AdminClient java class passing it the deploy.wsdd file as an argument.


<target name="deployWebservice" description="Deploys the webservice onto the server, using the wsdd file and Axis' AdminClient">
<java classname="org.apache.axis.client.AdminClient" classpathref="classpath" fork="true">
<arg value="${wsdd.dir}/com/jaikiran/deploy.wsdd"/>
</java>
</target>


Now that we have deployed the webservice, lets verify whether its deployed successfully. Let's again go to http://localhost:8080/axis . Here again lets click the "List" link to see all the available webservices. This time you will notice that our HelloWorld service is also listed over here. You can view the wsdl of this webservice, by clicking the wsdl link next to it. Our webservice has been deployed at http://localhost:8080/axis/services/HelloWorld as you can see from the wsdl contents. So let's hit this url. You will see a message like:

HelloWorld

Hi there, this is an AXIS service!
Perhaps there will be a form for invoking the service here...


4) Java client for accessing the webservice that we created:

Let's now create a simple java client which accesses the webservice that we just deployed on the server.
You might remember that in step 3c, we even created the stubs required to access the webservice. We will use these stubs in our java client to access the webservice. Here's the simple java client:


package org.myapp.service.client;

import com.jaikiran.HelloWorldService;
import com.jaikiran.HelloWorldServiceServiceLocator;

public class ServiceClient {

/**
* @param args
*/
public static void main(String[] args) {

try {

//Get hold of the webservice using the locator provided by Axis
HelloWorldService helloWorldService = new HelloWorldServiceServiceLocator().getHelloWorld();

//Invoke the method on the webservice
String message = helloWorldService.sayHelloTo("someUserName");

System.out.println("Client received: " + message);

} catch (Exception e) {
e.printStackTrace();
}
}

}


In this class you will notice that we are using HelloWorldServiceServiceLocator and the HelloWorldService which Axis generated for us. Running this client will invoke the webservice deployed on the server.


5) Ant build file

Here's the complete build.xml that i used as part of this example:


<?xml version="1.0" encoding="UTF-8"?>
<project name="MyApp" basedir=".." default="compile">

<property name="conf.dir" location="${basedir}/conf" />
<property name="build.dir" location="${basedir}/build"/>
<property name="classes.dir" location="${build.dir}/classes" />
<property name="compile.src" location="${basedir}/src" />
<property name="lib" location="${basedir}/lib" />
<property name="wsdl.dir" location="${build.dir}/wsdl" />
<property name="wsdd.dir" location="${build.dir}/wsdd" />


<!-- Sets classpath for build -->
<path id="classpath">
<pathelement path="${java.class.path}" />
<fileset dir="${lib}">
<include name="**/*.jar" />
</fileset>
<pathelement path="${classes.dir}" />
</path>



<taskdef name="axis-java2wsdl" classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask" >
<classpath refid="classpath"/>
</taskdef>

<taskdef name="axis-wsdl2java" classname="org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask" >
<classpath refid="classpath"/>
</taskdef>

<target name="clean-all" description="Cleans(Deletes) the contents of all output directories">
<delete dir="${classes.dir}"/>
<delete dir="${wsdl.dir}"/>
<delete dir="${wsdd.dir}"/>
</target>

<target name="build-all" description="Builds the entire project" depends="clean-all,compile,generateWSDL,generateWSDD,compileStubs,compile-client">

</target>

<target name="compile" description="Compiles java source code">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" classpathref="classpath" fork="true" srcdir="${compile.src}" excludes="org/myapp/service/client/**/*.java"/>
</target>

<target name="compile-client" description="Compiles the webservice client" depends="compile">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" classpathref="classpath" fork="true" srcdir="${compile.src}" includes="org/myapp/service/client/**/*.java"/>
</target>

<target name="generateWSDL" description="Generates wsdl files from the java service interfaces">
<mkdir dir="${wsdl.dir}"/>
<axis-java2wsdl classpathref="classpath"
output="${wsdl.dir}/HelloWorld.wsdl"
location="http://localhost:8080/axis/services/HelloWorld"
namespace="http://jaikiran.com"
classname="org.myapp.service.HelloWorldService">
<mapping namespace="http://jaikiran.com" package="org.myapp.service"/>
</axis-java2wsdl>
</target>

<target name="generateWSDD" description="Generates wsdd files from the wsdl files">
<mkdir dir="${wsdd.dir}"/>
<axis-wsdl2java
output="${wsdd.dir}"
deployscope="Application"
serverside="true"
url="${wsdl.dir}\HelloWorld.wsdl">
</axis-wsdl2java>
</target>

<target name="compileStubs" description="Compiles the java classes(stubs) generated by Axis using the wsdl2java task">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" classpathref="classpath" fork="true" srcdir="${wsdd.dir}"/>
</target>

<target name="deployWebservice" description="Deploys the webservice onto the server, using the wsdd file and Axis' AdminClient">
<java classname="org.apache.axis.client.AdminClient" classpathref="classpath" fork="true">
<arg value="${wsdd.dir}/com/jaikiran/deploy.wsdd"/>
</java>
</target>

<target name="undeployWebservice" description="Undeploys webservice using the wsdd file and the Axis' AdminClient">
<java classname="org.apache.axis.client.AdminClient" classpathref="classpath" fork="true">
<arg value="${wsdd.dir}/com/jaikiran/undeploy.wsdd"/>
</java>
</target>

<target name="runWebserviceClient" description="Executes the java client which access the webservice">
<java classname="org.myapp.service.client.ServiceClient" classpathref="classpath" fork="true"/>
</target>

</project>



6) The project directory structure

This is how my project's directory structure looks like (just in case you want to create a similar one):


Sample Webservice
|
|
|------------- src
| |
| |---- org
| |--- myapp
| |---- service
| |------- HelloWorldService.java
| |
| |------- HelloWorldServiceImpl.java
| |
| |----- client
| |-------- ServiceClient.java
|
|
|------------- build
| |----- build.xml
|
|
|------------- lib
|
|----- activation.jar
|----- axis.jar
|----- axis-ant.jar
|----- commons-discovery-0.2.jar
|----- commons-logging-1.0.4.jar
|----- jaxrpc.jar
|----- mailapi_1_3_1.jar
|----- saaj.jar
|----- wsdl4j-1.5.1.jar