lundi 6 octobre 2008

How to deal with Pessimistic Locking

There are severals way to perform pessimistic locking :
  1. Using a direct locking mechanism in database (vendor specific)
  2. Using query hints defined statically in named meta data (vendor specific)
  3. Using a Query api that defines the lock programatically (vendor specific)
  4. Using vendor implementation artifacts (vendor specific)
  5. Using the optimistic lock mechanism of JPA
Database locking mechanism
Database lock mechanism is vendor specific but it is very efficient.
For Oracle database, the lock is performed while doing a 'select for update' on a row of a database table.
See here under to get a sample that explains how to lock an entity:

public MyEntity lock( long id)
{
Query query = entityManager.createNativeQuery("select e.* from myentity e where id=?1 for update");
query.setParameter(1, id);
MyEntity found = null;
try {
found = query.getSingleResult();
} catch(EntityNotFoundException e) {} catch(NoResultException e) {}
if(found != null) entityManager.refresh(found);
return found;
}


The lock is acquired until the calling transaction commits or rollbacks.
If a transaction acquires the lock, all others transactions will be blocked on the query.getSingleResult() until the first one exists its transaction.
This mechanism is database specific and is not portable if you consider changing your database provider.

Query hints
Query hints are vendor directives that may be defined statically in named query metadata (annotations or XML).
They are applied at execution time while executing or flushing the query.
See here under to get a sample :


@NamedQuery(name=“MyEntity.loadAndLock”,
query=“SELECT e FROM MyEntity e WHERE e.id = :id”,
hints={
@QueryHint( name=“toplink.pessimistic-lock”, value=“Lock”),
@QueryHint( name=“openjpa.ReadLockLevel”,value=“write”)
}
)

As you can see the toplink.pessimistic-lock hint is dedicated to the TopLink midleware and could not be used in an other provider like Hibernate.
This source code is not portable to an other environment.

Dynamic query hints
Some time you may want to lock dynamically a query using the Query API.
This is more flexible because the lock could be applied on any object and any query without a static definition.

Query query = em.createQuery( “SELECT e FROM MyEntity e WHERE e.id = :id”);
query.setHint( “toplink.pessimistic-lock”,“Lock”);
query.setHint( “openjpa.ReadLockLevel” “write”);
query.setParameter(“id”, objectId)
query.getResultList();



Vendor artifacts
There is an other way to perform a lock using the specific API of the database mapping midleware. For example with Toplink you can write :


public MyEntity loadAndLock( int id)
{
MyEntity e = entityManager.find( MyEntity.class, id);

UnitOfWork uow = (TopLinkEntityManager)entityManager.getUnitOfWork();
uow.refreshAndLockObject(e, LOCK);
return e;
}


you can write also the following source code :

public MyEntity loadAndLock(int id)
{
Query q = entityManager.createQuery( “SELECT e FROM MyEntity e WHERE e.id = :id”);
q.setParameter( “id”, id);
((ObjectLevelReadQuery)((TopLinkQuery)q.getDatabaseQuery())).acquireLocks();
return q.getSingleResult();
}


As you can see, the code is specific to the supplier of the persistence layer and can not in any way be related to a different environment


Lock using JPA feature
At the moment JPA supports only Optimistic Locking but does not support the pessimistic locking mechanism. But if you are familiar with the lock feature of the entity manager you will realize that it is possible to use the traditional lock method to perform a pessimistic locking.

The following source code do almost the same thing as the 'select for update' in oracle database but is portable whatever the environment :

MyEntity entity = entityManager.find( MyEntity.class, id);
entityManager.lock ( entity, LockModeType.WRITE);
entityManager.flush ();


The use of the lock mechanism at entity involves the use of a field-level version on this entity :

@entity
public class Myentity
{
@Id
private Integer id;

@Version
private int version;

...

public int getVersion () {
return version;
}

public void setVersion (int version) {
this.version = version;
}
}


What's happening while locking ?
The first thread that acquires the lock is not blocked but a second one that trys to access the same entity is blocked on the flush() method.
When the first thread commits or rollbacks, the blocked one receive a javax.persistence.OptimisticLockException. This mechanism ensure that the second thread does not take into account data that would have been affected by the first one.
As a result, the source code must be aware that an exception can be lifted and must implement a mechanism to retry.

Note that when the flush() throw an exception, the current transaction is marked for rollback and can not be used for transactional purposes. In this way each retry must be performed using a new transaction.

mardi 23 septembre 2008

How to deal with abstract classes in WS signature

Dealing with concrete classes in web service signature is very easy but what is happening when dealing with abstract classes ? It doesn't work ! Really ? Take a look at this sample...

In the following example, i will try to call a web service method that referes in its signature an abstract class.
I developpe the Person class where i inherit two concrete classes: Employee and Manager. I create after a web service where i declare a methode to create both an Employee or a Manager : public void createPerson( Person aPerson);
If you try this without annotation (except for @WebService, @WebMethod and @WebParam) you will have an exception some time when generating the wsdl file, some time when you try to use it from a client.

To make them work, you need to help the ws generator with binding annotation (JAXB annototion). I give you the solution :

Defining your object model
for the abstract class

package test.example5.model;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;

@XmlType( name="Person")
public abstract class Person {
@XmlAttribute(name="firstName", required=true)
private String firstName;
@XmlAttribute(name="lastName", required=true)
private String lastName;

public Person() {}

public String getFirstName () {
return firstName;
}

public void setFirstName (String firstName) {
this.firstName = firstName;
}

public String getLastName () {
return lastName;
}

public void setLastName (String lastName) {
this.lastName = lastName;
}
}


For the concrete one :

package test.example5.model;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement( name="Employee")
@XmlType( name="Employee")
public class Employee extends Person {
@XmlAttribute( name="managerName", required= false)
private String managerName;

public Employee()
{
super();
}

public String getManagerName () {
return managerName;
}

public void setManagerName (String managerName) {
this.managerName = managerName;
}
}


package test.example5.model;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement( name="Manager")
@XmlType( name="Manager")
public class Manager extends Person {
@XmlAttribute( name="teamName", required=false)
private String teamName;

public Manager()
{
super();
}

public String getTeamName () {
return teamName;
}

public void setTeamName (String teamName) {
this.teamName = teamName;
}
}


Now you need to create yourself an object factory that will be used to generate all these classes

package test.example5.model;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {
//--------------------------------------------------------------------------
// Static members
//--------------------------------------------------------------------------

private final static QName _Person_QNAME = new QName("http://model.example5.test/", "Person");
private final static QName _Employee_QNAME = new QName("http://model.example5.test/", "Employee");
private final static QName _Manager_QNAME = new QName("http://model.example5.test/", "Manager");

//-------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------

/**
* Create a new ObjectFactory that can be used to create new instances
*
*/
public ObjectFactory() {
}

/**
* Create an instance of {@link JAXBElement }{@code <}{@link Person}{@code >}}
*
*/
@XmlElementDecl(namespace = "http://model.example5.test/", name = "Person")
public JAXBElement createPerson(Person value) {
return new JAXBElement(_Person_QNAME, Person.class, null, value);
}

/**
* Create an instance of {@link Employee }
*
*/
public Employee createEmployee() {
return new Employee();
}


/**
* Create an instance of {@link JAXBElement }{@code <}{@link Employee}{@code >}}
*
*/
@XmlElementDecl(namespace = "http://model.example5.test/", name = "Employee")
public JAXBElement createEmployee(Employee value) {
return new JAXBElement(_Employee_QNAME, Employee.class, null, value);
}

/**
* Create an instance of {@link Manager }
*
*/
public Manager createManager() {
return new Manager();
}


/**
* Create an instance of {@link JAXBElement }{@code <}{@link Manager}{@code >}}
*
*/
@XmlElementDecl(namespace = "http://model.example5.test/", name = "Manager")
public JAXBElement createManager(Manager value) {
return new JAXBElement(_Manager_QNAME, Manager.class, null, value);
}
}
The @XmlRegistry declares that the class contains the methods use to map your data model object to JAXBElement use to serialize your objects in an XML form. for more information see http://java.sun.com/javase/6/docs/api/javax/xml/bind/annotation/XmlElementDecl.html.

Note also the difference between the factory methods of the abstract classe and one for the concrete classes. in this class, you will see how is manage the binding for all object managed by web services.

Now you need to create a specific class called package-info.java :

@javax.xml.bind.annotation.XmlSchema(namespace = "http://model.example5.test/")
@XmlAccessorType(XmlAccessType.NONE)
package test.example5.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;


To resume, in your model package you will have the following classes:
* Person.java
* Employee.java
* Manager.java
* ObjectFactory.java
* package-info.java


Defining your web service
Now, you can code your web service :

package test.example5.service;

import javax.ejb.Stateless;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;

import test.example5.model.Employee;
import test.example5.model.Manager;
import test.example5.model.Person;

@WebService( name="TestExample5", serviceName="TestExample5", targetNamespace="http://techtip.com/samples/example5")
@XmlSeeAlso({
test.example5.model.ObjectFactory.class
})

@Stateless
public class TestExample5 {

@WebMethod( operationName="createPerson")
@Oneway
public void createPerson( Person person)
{
if( person instanceof Employee)
{
System.out.println( "Creating Employee = [firstName=" + person.getFirstName ()
+ ";lastName=" + person.getLastName ()
+ ";managerName=" + ((Employee)person).getManagerName () + "]"
);
} else if( person instanceof Manager)
{
System.out.println( "Creating Manager = [firstName=" + person.getFirstName ()
+ ";lastName=" + person.getLastName ()
+ ";teamName=" + ((Manager)person).getTeamName () + "]"
);
} else {
System.out.println ( "Person not managed");
}
}

}

The @XmlSeeAlso annotation is very important, it provides the class that is in change of the binding of the objects manage in web service signature (the ObjectFactory of your data model).
If you have several packages because you have several data model, you can declare all the Object factory in the @XmlSeeAlso.

So use wsgen to generate the wsdl and associated files, you will have :
* TestExample5.wsdl
* TestExample5_schema1.xsd
* TestExample5_schema2.xsd

This is the content of the TestExample5.wsdl :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.2-hudson-182-RC1. -->
<definitions targetNamespace="http://techtip.com/samples/example5" name="TestExample5" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:tns="http://techtip.com/samples/example5" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsp:UsingPolicy/>
<wsp:Policy wsu:Id="TestExample5PortBinding_createPerson_WSAT_Policy">
<wsp:ExactlyOne>
<wsp:All>
<ns1:ATAlwaysCapability wsp:Optional="false" xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/10/wsat"/>
<ns2:ATAssertion ns3:Optional="true" wsp:Optional="true" xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/10/wsat" xmlns:ns3="http://schemas.xmlsoap.org/ws/2002/12/policy"/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
<types>
<xsd:schema>
<xsd:import namespace="http://techtip.com/samples/example5" schemaLocation="TestExample5_schema1.xsd"/>
</xsd:schema>
<xsd:schema>
<xsd:import namespace="http://model.example5.test/" schemaLocation="TestExample5_schema2.xsd"/>
</xsd:schema>
</types>
<message name="createPerson">
<part name="parameters" element="tns:createPerson"/>
</message>
<portType name="TestExample5">
<operation name="createPerson">
<input message="tns:createPerson"/>
</operation>
</portType>
<binding name="TestExample5PortBinding" type="tns:TestExample5">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="createPerson">
<wsp:PolicyReference URI="#TestExample5PortBinding_createPerson_WSAT_Policy"/>
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
</operation>
</binding>
<service name="TestExample5">
<port name="TestExample5Port" binding="tns:TestExample5PortBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>


For the TestExample5_schema1.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://techtip.com/samples/example5" xmlns:ns1="http://model.example5.test/" xmlns:tns="http://techtip.com/samples/example5" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:import namespace="http://model.example5.test/" schemaLocation="TestExample5_schema2.xsd"/>

<xs:element name="createPerson" type="tns:createPerson"/>

<xs:complexType name="createPerson">
<xs:sequence>
<xs:element name="arg0" type="ns1:Person" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>


For the TestExample5_schema2.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://model.example5.test/" xmlns:tns="http://model.example5.test/" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="Employee" nillable="true" type="tns:Employee"/>

<xs:element name="Manager" nillable="true" type="tns:Manager"/>

<xs:element name="Person" nillable="true" type="tns:Person"/>

<xs:complexType name="Person" abstract="true">
<xs:sequence/>
<xs:attribute name="firstName" type="xs:string" use="required"/>
<xs:attribute name="lastName" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="Employee">
<xs:complexContent>
<xs:extension base="tns:Person">
<xs:sequence/>
<xs:attribute name="managerName" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:complexType name="Manager">
<xs:complexContent>
<xs:extension base="tns:Person">
<xs:sequence/>
<xs:attribute name="teamName" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>


You will see that with all the binding annotations, wsgen generates the Person, Employee and Manager types used by our web service.

The WS client
This is the client code that tests Employee and Manager creation :

package test.example5;

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

import javax.xml.namespace.QName;

import org.junit.BeforeClass;
import org.junit.Test;

import test.example5.generated.Employee;
import test.example5.generated.Manager;


public class TestExample5 {
private static test.example5.generated.TestExample5 testManager;
private static final String HOST_NAME = "localhost";
private static final String PORT_NUMBER = "8081";
private static final String TARGET_NAMESPACE = "http://techtip.com/samples/example5";

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

test.example5.generated.TestExample5_Service service = new test.example5.generated.TestExample5_Service(
wsdlLocation, serviceName
);

testManager = service.getTestExample5Port ();

}

@Test
public void testCreatePerson() throws Exception {
Employee employee = new Employee();
employee.setFirstName ( "Franck");
employee.setLastName ( "Mosse");
employee.setManagerName ( "BigBoss");

testManager.createPerson ( employee);

Manager manager = new Manager();
manager.setFirstName ( "Martin");
manager.setLastName ( "Dupond");
manager.setTeamName ( "IT");

testManager.createPerson ( manager);
}

}

How to use a wsdl file locally from a client code

From a WS client point of view, it would be interesting to retrieve a wsdl file locally instead of calling a remote web server. This method garantee that if the server add some new feature in the WS port you use, you do not have to rebuild your client code.

To do so, you need to generate your stub specifiying a local wsdl file. In this way, the path to access the wsdl file is relative to the path of the package where you generate your stubs.

For example with wsimport you can ask it to generate the stub in the test.example.generated package ( wsimport … -p test.example.generated …) and referes locally your wsdl file (if wsdl files stored in /wsd => wsimport … -p test.example.generated –wsdllocation ../../../wsdl/mywsdlfile.xml).

What is the impact on you client code ?
For example if you try to use a web service called MyHelloWorld and if you generate your stubs in the test.example.generated package, your client code will look like :

test.example.generated.MyHelloWorldService service = new test.example.generated.MyHelloWorldService();
test.example.generated.MyHelloWorld helloPort = service.getMyHelloWorldPort();
BindingProvider provider = (BindingProvider) helloPort;
provider.getRequestContext().put(
BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"http://localhost:8080/MyHelloWorld/MyHelloWorld"
);


How to package your client
To package you client jar, you need to add :
  • the stubs generated by the wsimport utility
  • the wsdl files and associated xsd,
  • and your client code.
That's all...

vendredi 19 septembre 2008

How to register SwiftMQ broken in a Glassfish cluster

To register the swift MQ broker in cluster environnement you can use the following asadmin commands...

First you need to deploy the ressouce archive that contains the swift connector
asadmin deploy --host <hostname> --port <portNumber> -u admin --passwordfile=asadmin_password.txt   --target=domain --name swiftmq_rar swiftmq.rar


Replace the <hostname> and <portNumber> with respectiveley the host name and port number where is located the DAS that manages the cluster.

Register after the connector in the DAS
asadmin create-resource-adapter-config --host <hostname> --port <portNumber> -u admin --passwordfile=asadmin_password.txt   --property AllowOnePhase=false:IntraVM=false:DebugMode=false:ProviderURL="smqp\://<SWIFT_HOST>\:4001/timeout\=10000\;reconnect\=true\;retrydelay\=5000\;maxretries\=120" swiftmq_rar


The connector is registered under the name swiftmq_rar. You can see it through the admin console under the item "Applications/Connector modules".
Replace <SWIFT_HOST> with the name of the host where is located the broker

Now it's time to register the connection pool and connection factory which connects to the broker
asadmin create-connector-connection-pool --host <hostname> --port <portNumber> -u admin --passwordfile=asadmin_password.txt   --steadypoolsize 20 --maxpoolsize 100 --poolresize 2 --maxwait 60000 --raname swiftmq_rar --transactionsup
port XATransaction --connectiondefinition javax.jms.ConnectionFactory --property ConnectionFactoryName=ConnectionFactory swiftmq-shared-connection-pool


the connection pool is named swiftmq-shared-connection-pool.

asadmin create-connector-resource --host <hostname> --port <portNumber> -u admin --passwordfile=asadmin_password.txt   --target=domain --poolname swiftmq-shared-connection-pool  jms/ConnectionFactory


The jms/ConnectionFactory is linked with the swiftmq-shared-connection-pool;

Registering of a queue
Now we can register a queue. For the example, i will register the queue Notify on the router router1 on swift.
asadmin create-admin-object --host <hostname> --port <portNumber> -u admin --passwordfile=asadmin_password.txt   --target=domain --restype javax.jms.Queue --raname swiftmq_rar --property QueueName=Notify@router1  jms/Notify


The password file must contains the admin pasword and some time the master passord
AS_ADMIN_PASSWORD=adminadmin
AS_ADMIN_MASTERPASSWORD=changeit

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.

jeudi 18 septembre 2008

Begining with WebServices on Glassfish

Welcome to my Java Technologies Tech Tips.
Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE) and Java Platform, Enterprise Edition 5 (Java EE 5).

This post covers:
* Developing Web Services using JAX-WS for beginers
* Generating the associated wsdl
* Writing a standalone client

These tips were developed using an open source implementation of Java EE 5 called GlassFish.

Developing a simple Web Service

To build the web service :
* Create a Java project that integrates your application server libraries ( for me the javaee.jar under my glassfish library path)
* Write an endpoint implementation class under your project.
* Compile the endpoint implementation class.
* Optionally generate portable artifacts required for web service execution.
* Package the web service as a WAR or JAR file and deploy it.


Writing the end point

First of all, under my IDE, i create a package where i will write my web service.For my sample, i decide to create the test.example1 package.
Under it i create a class named PeopleManager.java that will publish a sample method with simple type arguments. See the code here under :

package test.example1;

import javax.ejb.Stateless;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService( name="PeopleManager",
serviceName="PeopleManager",
targetNamespace="http://techtip.com/samples/example1"
)

@Stateless

public class PeopleManager
{
@WebMethod( operationName="createPerson")
@Oneway
public void createPerson ( @WebParam(name="firstName") String firstName,
@WebParam(name="lastName") String lastName,
@WebParam(name="age") int age
)
{
System.out.println ( "Creating person [firstName=" + firstName
+ "; lastName=" + lastName + ";age=" + age + "]"
);
}
}

As you can see, the class is annotated @WebService and specify the name of the service and the namespace to use. The class is also annotated @Stateless but this is not important for the sample, it is just because i would like to deploy the web service through a jar instead of a WAR archive.
The class provide one method annotated @WebMethod and parameters annotated @WebParam

Compile the class

For that, you can use your IDE build functionnality or for the purist the javac command line (don't forget to reference the javaee.jar in your classpath).

Build your jar

Once your class is compiled, build a jar that contains the class using your favourite IDE or for the purist using the jar command line.

Deploy your jar
Start your application server using the asadmin start-domain command line and use the graphical interface to depoy your archive.
If all is ok, you can see your jar deployed under the item 'Applications/EJB Modules' of the interface and also the PeopleManager Web Service under the item 'Web Services'.
The wsdl of the service could be accessed using your navigator at http://localhost:8080/PeopleManager/PeopleManager?wsdl
The xsd could be reached at
http://localhost:8080/PeopleManager/PeopleManager?xsd=1

Building the wsdl manually
Some times it is necessary to build the wsdl in order to provide the service decription to a client program. In this way the client classes could be generated without connection to the server hosting the web service.
To generate these artifacts you can use the wsgen program privided with your jdk or your application server.
The wsgen.bat file is located in the GLASSFISH_HOME/bin directory.
As it is not easy to build the wsdl manually, you can use the following ant script that is very usefull :

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

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

<path id="jee.cp">
<pathelement location="${appserver.home}/lib/toplink-essentials.jar" />
<pathelement location="${appserver.home}/lib/javaee.jar" />
</path>

<!-- setup build classpath -->
<path id="build.cp">
<pathelement location="${build.home}" />
</path>

<path id="module.cp">
<dirset dir="../">
<include name="**/bin"/>
</dirset>
</path>

<!-- 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="${wsdl.home}"/>
<mkdir dir="${srcgen.home}"/>
</target>

<target name="j2wsdl" depends="setup">
<wsgen sei="test.example1.PeopleManager"
genwsdl="true"
resourcedestdir="${wsdl.home}"
sourcedestdir="${srcgen.home}"
destdir="${build.home}"
extension="true"
keep="false"
verbose="true">
<classpath refid="build.cp"/>
<classpath refid="module.cp"/>
<classpath refid="tool.cp"/>
<classpath refid="jee.cp"/>
</wsgen>
</target>

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

</project>


The appserver.home variable contains the location of the application server ( for me "D:\glassfish-v2ur1").
The project.home variable contains the location of your project
The build.home variable contains the directory where are generated your classes (for me "${project.home}\classes")
The wsdl.home contains the location of the wsdl files generated by wsgen (for me "${project.home}\dist\wsdl")
The srcgen.home variable contains the directory where wsgen can write its java classes (
${project.home}\dist\srcgen\server)

You can use also the command line :
wsgen -classpath "D:\Documents and Settings\fmosse\workspace\TestWebService\classes;D:\glassfish-
v2ur1\lib\webservices-tools.jar;D:\glassfish-v2ur1\lib\toplink-essentials.jar;D:\glassfish-v2ur1\lib\javaee.jar" -d "D:\
Documents and Settings\fmosse\workspace\TestWebService\classes" -extension -wsdl -r "D:\Documents and Settings\fmosse\wo
rkspace\TestWebService\dist\wsdl" -s "D:\Documents and Settings\fmosse\workspace\TestWebService\dist\srcgen\server" -ver
bose test.example1.PeopleManager

Two files are generated by wsgen under ${project.home}\dist\wsdl directory :
* PeopleManager.wsdl
* PoepleManager_schema1.xsd


PeopleManager.wsdl

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.2_01-hudson-189-. -->
<definitions targetNamespace="http://techtip.com/samples/example1" name="PeopleManager" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:tns="http://techtip.com/samples/example1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsp:UsingPolicy/>
<wsp:Policy wsu:Id="PeopleManagerPortBinding_createPerson_WSAT_Policy">
<wsp:ExactlyOne>
<wsp:All>
<ns1:ATAlwaysCapability wsp:Optional="false" xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/10/wsat"/>
<ns2:ATAssertion ns3:Optional="true" wsp:Optional="true" xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/10/wsat" xmlns:ns3="http://schemas.xmlsoap.org/ws/2002/12/policy"/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
<types>
<xsd:schema>
<xsd:import namespace="http://techtip.com/samples/example1" schemaLocation="PeopleManager_schema1.xsd"/>
</xsd:schema>
</types>
<message name="createPerson">
<part name="parameters" element="tns:createPerson"/>
</message>
<portType name="PeopleManager">
<operation name="createPerson">
<input message="tns:createPerson"/>
</operation>
</portType>
<binding name="PeopleManagerPortBinding" type="tns:PeopleManager">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="createPerson">
<wsp:PolicyReference URI="#PeopleManagerPortBinding_createPerson_WSAT_Policy"/>
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
</operation>
</binding>
<service name="PeopleManager">
<port name="PeopleManagerPort" binding="tns:PeopleManagerPortBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>





PoepleManager_schema1.xsd

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://techtip.com/samples/example1" xmlns:tns="http://techtip.com/samples/example1" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="createPerson" type="tns:createPerson"/>

<xs:complexType name="createPerson">
<xs:sequence>
<xs:element name="firstName" type="xs:string" minOccurs="0"/>
<xs:element name="lastName" type="xs:string" minOccurs="0"/>
<xs:element name="age" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema>