vendredi 19 septembre 2008

Writing a simple WebService standalone client

In this post we will see how to write a standalone client that invoques the previous web service.

Developing a simple Web Service

To build the web service client :
* Create a Java project that integrates your application server libraries ( for me the javaee.jar under my glassfish library path)
* Generate the artifacts use to give access to the remote web service
* Write the client.
* Compile and run the client.


Generating client side artifacts
In the client code, you need to have the implementation of the service you invoke remotely. To have these classes (Service and Port) you need to generate artifacts using the wsimport utility.
To do that, i provide to you an ant script that generates the classes from the wsdl located locally.
First get the wsdl and xsd files previously generated in the server project from the dist/wsdl directory and copy these files in the dist/wsdl directory of your client project.
Use then the ant script here under to generate your artifacts :

<project name="ws-import" default="build" basedir=".">
<property name="appserver.home" value="D:\glassfish-v2ur1" />
<property name="project.home" value="D:\Documents and Settings\fmosse\workspace\TestClientWebService" />
<property name="build.home" value="${project.home}\classes" />
<property name="srcgen.home" value="${project.home}\src" />
<property name="wsdl.home" value="${project.home}\dist\wsdl" />

<!-- setup Metro runtime classpath -->
<path id="runtime.cp">
<fileset dir="${appserver.home}/lib" includes="*.jar" excludes="webservices-tools.jar" />
<pathelement location="${build.home}" />
</path>

<!-- setup Metro tooltime classpath -->
<path id="tool.cp">
<path refid="runtime.cp" />
<pathelement location="${appserver.home}/lib/webservices-tools.jar" />
</path>

<!-- Setup Wsimport ant task. You would use this task in WSDL to Java case to compile a WSDL and generate Java classes. -->
<taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport">
<classpath refid="tool.cp" />
</taskdef>

<!-- Setup Wsgen ant task. You would use this task in Java to WSDL case to generate a WSDL or wrapper classes. -->
<taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen">
<classpath refid="tool.cp" />
</taskdef>

<target name="setup">
<mkdir dir="${build.home}" />
<mkdir dir="${srcgen.home}"/>
</target>

<target name="wsdl2j" depends="setup">
<wsimport
fork="false"
debug="true"
extension="true"
keep="true"
destdir="${build.home}/"
sourceDestDir="${srcgen.home}"
verbose="true"
package="test.example1.generated"
wsdl="${wsdl.home}\PeopleManager.wsdl">
</wsimport>
</target>

<target name="build" depends="wsdl2j">
<javac debug="true"
destdir="${build.home}"
srcdir="${srcgen.home}"
includes="**/*.java">
<classpath refid="tool.cp" />
</javac>
</target>

<target name="clean">
<delete dir="${build.home}" />
</target>

<target name="usage">
<echo message="Usage: " />
<echo message="ant client (runs wsimport and compiles client class and then runs the client" />
</target>

</project>


The classes will be generated in the test.example1.generated package as specified in the wsimport command directly under your source directory of your project (see sourceDestDir="${srcgen.home}").

To generate the artifacts using a command line you can type :
wsimport -d "D:\Documents and Settings\fmosse\workspace\TestClientWebService\classes" -extension -g -keep -s "D:\Documents and Settings\fmosse\workspace\TestClientWebService\src" -verbose "D:\Documents and Settings\fmosse\workspace\TestClientWebService\dist\wsdl\PeopleManager.wsdl" -p test.example1.generated


As you can see the wsdl is not retrieve remotly but directly in your local directory "D:\Documents and Settings\fmosse\workspace\TestClientWebService\dist\wsdl\PeopleManager.wsdl". In this way, the artifacts does not contain any reference on the location of the web service.

Writing the Client
Once the artifacts generated in your sources directory, it is time to write the client code.
The following program, TestPeopleManager, is a standalone client program provided with the sample code for this tip. It is a also annotated with JUnit annotation. It invokes the createPerson operation on the deployed service.

package test.example1;

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

import javax.xml.namespace.QName;

import org.junit.BeforeClass;
import org.junit.Test;
import test.example1.generated.*;

public class TestPeopleManager {

private static PeopleManager peopleManager;
private static final String HOST_NAME = "localhost";
private static final String PORT_NUMBER = "8080";
private static final String TARGET_NAMESPACE = "http://techtip.com/samples/example1";

@BeforeClass
public static void beforeClass () throws MalformedURLException
{
String wsdlUrl = "http://" + HOST_NAME + ":" + PORT_NUMBER + "/PeopleManager/PeopleManager?wsdl";
URL wsdlLocation = new URL( wsdlUrl);
QName serviceName = new QName(
TARGET_NAMESPACE,
"PeopleManager"
);

PeopleManager_Service service = new PeopleManager_Service(
wsdlLocation, serviceName
);

peopleManager = service.getPeopleManagerPort ();

}

@Test
public void testCreatePerson() throws Exception {
peopleManager.createPerson ( "Franck", "Mosse", 40);
}
}



The beforeClass method is executed first(due to @BeforeClass annotation). It retrieves the service and port used to communication with the service deployed in the previous post.
The QName describes the service to lookup and the URL the location of the wsdl.
In this way the client will connect to the remote server to get the wsdl file before accessing the web service.

If you does not want that the client calls the server to get the wsdl file, you can write the following piece of code :


PeopleManager_Service service = new PeopleManager_Service();
PeopleManager proxy = service.getPeopleManagerPort ();

BindingProvider provider = (BindingProvider) proxy;
provider.getRequestContext().put(
BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"http://localhost:8080/PeopleManager/PeopleManager"
);
proxy.createPerson ( "Franck", "Mosse", 40);


In this way the service use the url defined at the artifacts generation.

Packaging your client
If you want to package your client code in a jar, there is no problem when you use the code that retrieve the wsdl directly from the server where is deployed the web service. Just integrate the artifacts in your archive and it will be ok.

It is different when you use the second method, because when we generate the artifacts we specify an absolute path (remember "D:\Documents and Settings\fmosse\workspace\TestClientWebService\dist\wsdl\PeopleManager.wsdl" in the wsimport command).
It is then possible to set the wsdl location to use in the artifacts and make it relative to the genereted service class.
So if we consider that when making the archive, the wsdl file will be located in the /wsdl directory, we can write the following wsdl location for the generated service defined in package test.example1.generated.
The wsdl location is then ../../../wsdl/PoepleManager.wsdl and is relative to the package test.example1.generated.

In this way the wsimport command line becomes :
wsimport -d "D:\Documents and Settings\fmosse\workspace\TestClientWebService\classes" -extension -g -keep -s "D:\Documents and Settings\fmosse\workspace\TestClientWebService\src" -verbose "D:\Documents and Settings\fmosse\workspace\TestClientWebService\dist\wsdl\PeopleManager.wsdl" -p test.example1.generated -wsdllocation ../../../wsdl/PoepleManager.wsdl

Check now your generated code and you will see that the localtion is now ../../../wsdl/PoepleManager.wsdl.