Tuesday, July 22, 2008

Parameter customization in JAX-WS

After many months on Oracle BPEL, I am testing Apache ODE (Apache BPEL engine). ODE provides a web service which allows to administer the engine including deployment. To simplify developments, I have decided to create a deployment client by using JAX-WS.

To generate the different classes corresponding to the ODE Web Service, I have used the wsimport tool and the following error is appeared :

[ERROR] Invalid operation "deploy", can't generate java method parameter. Local name of the wrapper child "package" in the global element "{http://www.apache.org/ode/pmapi}deploy" is a java keyword. Use customization to change the parameter name.
line 138 of http://localhost:8080/ode/processes/DeploymentService?wsdl

the problem is coming from the Apache ODE wsdl and specially from this code :

...
<xsd:element name="deploy">
<xsd:complexType>
<xsd:sequence>
<xsd:element form="unqualified" name="name" type="xsd:string"/>
<xsd:element form="unqualified" name="package" type="ns30:package"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
...
<wsdl:message name="deployInput">
<wsdl:part name="parameters" element="ns0:deploy"/>
</wsdl:message>
...
<wsdl:portType name="DeploymentServicePortType">
...
<wsdl:operation name="deploy">
<wsdl:input message="tns:deployInput" Action="http://www.apache.org/ode/deployapi/DeploymentPortType/deployRequest"/>
<wsdl:output message="tns:deployOutput" wsaw:Action="http://www.apache.org/ode/deployapi/DeploymentPortType/deployResponse"/>
</wsdl:operation>
...


The error is easy to understand : you cannot transform in a java class an xml element which is named like a java keyword otherwise a compilation error would occur. The problem is that we cannot change the contract, it is Apache which provides it. What can we do ? the solution is JAX-WS Customization : it allows us to adapt the different generated classes.

For our case, we need to rename the package element by, per example, paramPackage. Firstly, we need to create a customization file (we will call it bindings.xml) :

<jaxws:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
wsdlLocation="http://localhost:8080/ode/processes/DeploymentService?wsdl">

<jaxws:bindings node="wsdl:definitions/wsdl:portType[@name='DeploymentServicePortType']/wsdl:operation[@name='deploy']">

<jaxws:parameter
part="wsdl:definitions/wsdl:message[@name='deployInput']/wsdl:part[@name='parameters']"
childElementName="package" name="paramPackage" />
</jaxws:bindings>
</jaxws:bindings>


Let's see this file in detail.

<jaxws:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
wsdlLocation="http://localhost:8080/ode/processes/DeploymentService?wsdl">


indicates that the source wsdl is wsdllocation.

<jaxws:bindings
node="wsdl:definitions/wsdl:portType[@name='DeploymentServicePortType']/wsdl:operation[@name='deploy']">


indicates that the target operation the parameter belongs to is deploy and that this operation has DeploymentServicePortType as port type.

<jaxws:parameter
part="wsdl:definitions/wsdl:message[@name='deployInput']/wsdl:part[@name='parameters']"
childElementName="package" name="paramPackage" />


indicates to wsimport that the xml element package must be mapped to paramPackage in Java. Moreover it is indicated that package element corresponds to parameters part of deployInput message.

Now you have just to use wsimport with the following options to apply the customization file on generated classes :

wsimport -keep -b <PROJECT_ROOT>/jaxws/bindings.xml -d <PROJECT_ROOT>/src -p com.javasoa.ws.ode http://localhost:8080/ode/processes/DeploymentService?wsdl


Finally, if you look at the generated web service interface, you can see that package parameter is renamed as paramPackage :

...
@WebService(name = "DeploymentServicePortType", targetNamespace = "http://www.apache.org/ode/deployapi")
@XmlSeeAlso({
ObjectFactory.class
})
public interface DeploymentServicePortType {
...
public DeployUnit deploy(
@WebParam(name = "name", targetNamespace = "")
String name,
@WebParam(name = "package", targetNamespace = "")
Package paramPackage);

Saturday, July 12, 2008

A simple JAX-WS Web Service with Eclipse

Here I am ! this my first post on my technical blog.

For this one, I am going to show you how to create an HelloWorld WS with JAX-WS RI (2.1.3) and Eclipse (3.4). We will use a contract-first method (WSDL to Java) to develop this first WS. We will deploy the WS on Tomcat (6.0).

Creating an Eclipse Project
  • Select the menu File → Other...
  • Select Web → Dynamic Web Project
  • Specify paths and Tomcat as the target runtime
  • Once you have created the project, copy the jar files from the directory <JAXWS_ROOT>/lib to <ECLIPSE_PROJECT>/WebContent/WEB-INF/lib then refresh the eclipse project
Creating the WS configuration files

Now we are going to configure the different JAX-WS configuration files :
  • Create the file <ECLIPSE_PROJECT>/WebContent/WEB-INF/wsdl/HelloWorld.wsdl (WS contract) :
<definitions name="HelloWorld" targetNamespace="http://java-soa.blogspot.com/"
xmlns:tns="http://java-soa.blogspot.com/" xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

<!-- types -->
<types>
<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" targetNamespace="http://java-soa.blogspot.com/">
<element name="name" type="xsd:string" />
<element name="response" type="xsd:string" />
</xsd:schema>
</types>

<!-- messages -->
<message name="name">
<part name="in" element="tns:name" />
</message>
<message name="response">
<part name="out" element="tns:response" />
</message>

<!-- operations -->
<portType name="HelloWorldPortType">
<operation name="hello">
<input message="tns:name" />
<output message="tns:response" />
</operation>
</portType>

<!-- binding -->
<binding name="HelloWorldBinding" type="tns:HelloWorldPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document" />
<operation name="hello">
<soap:operation soapAction="" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
</binding>

<!-- services -->
<service name="HelloWorldService">
<port name="HelloWorldPort" binding="tns:HelloWorldBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL" />
</port>
</service>
</definitions>
  • Create the file <ECLIPSE_PROJECT>/WebContent/WEB-INF/web.xml (Web deployment descriptor) :
<web-app version="2.4" mlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<description>HelloWorld</description>
<display-name>HelloWorld</display-name>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<description>JAX-WS endpoint - HelloWorld</description>
<display-name>HelloWorld</display-name>
<servlet-name>HelloWorldPort</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldPort</servlet-name>
<url-pattern>/helloWorld</url-pattern>
</servlet-mapping>

</web-app>
  • Create the file <ECLIPSE_PROJECT>/WebContent/WEB-INF/sun-jaxws.xml (JAX-WS RI deployment descriptor) :
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
<endpoint
name="HelloWorld"
implementation="com.javasoa.ws.impl.HelloWorldPortTypeImpl"
wsdl="WEB-INF/wsdl/HelloWorld.wsdl"
service='{http://java-soa.blogspot.com/}HelloWorldService'
port='{http://java-soa.blogspot.com/}HelloWorldPort'
url-pattern="/helloWorld"/>
</endpoints>
Configuring WsImport

the command wsimport generates the different java classes from the wsdl.
  • Select the menu Run → External Tools → External Tools Configuration
  • Click on Program then on button
  • Specify the following informations :
    • Name : CreateClassJAXWS
    • Location : <JAXWS_ROOT>/bin/wsimport.[bat|sh]
    • Working Directory : ${project_loc}
    • Arguments : -keep -d ${project_loc}/src -p com.javasoa.ws ${resource_loc}
  • Click on Close button
Arguments description :
  • keep : without this argument, sources files are deleted after generation
  • d : source path
  • p : source files target package
Generating Web Service classes
  • Select the wsdl file
  • Select the menu Run → External Tools → CreateClassJAXWS
  • Refresh the eclipse project
If you have followed the instructions, JAX-WS classes have been created :
  • com.javasoa.ws.HelloWorldPortType : this class defines the web service interface. The different annotations (WebService,WebMethod,WebParam...) indicates the Web Service informations (JAX-WS annotation list).
  • com.javasoa.ws.HelloWorldService : this class provides client methods (we will not use this class in this tutorial).
  • com.javasoa.ws.ObjectFactory : this class contains for each XML element a method providing the corresponding Java object.
Implementing the Web Service
  • Create the class com.javasoa.ws.impl with the following contents :
package com.javasoa.ws.impl;

import javax.jws.WebService;

import com.javasoa.ws.HelloWorldPortType;

/**
* HelloWorld WS implementation.
*/
@WebService(endpointInterface = "com.javasoa.ws.HelloWorldPortType")
public class HelloWorldPortTypeImpl implements HelloWorldPortType {

/** suffix : hello. */
private static final String SUFFIX_HELLO = "Hello ";

/////////////////////////////////////////////////////////////////////////

/**
* Say hello to in.
*
* @param in name
*
* @return response
*/
@Override
public String hello(String in) {
return (SUFFIX_HELLO + in);
}

}
The endpointInterface annotation indicates in which class (interface) find the web services description.
Now the web service the ready ! we just need to deploy it on Tomcat.

Configuring Tomcat in Eclipse
  • Select the menu Window → Preferences → Server → Runtime Environments
  • Click on Add button
  • Select Apache Tomcat V6.0 then click on Next button
  • In Tomcat Installation directory, specify the tomcat root path
  • Click on Finish button then on Ok Button
  • Select the menu Window → Show View → Servers
Deploying the Web Service
  • In the Server view, right-click and select the menu New → Server
  • Select Apache Tomcat V6.0 then click on Next button
  • Select the HelloWorld project then click on Add → button
  • Click on Finish button
  • Right-click on the server then select the menu Start
To be sure that your web service is ready, verify, in a web browser, that the URL http://localhost:8080/HelloWorld/helloWorld?wsdl is available. If the wsdl file is displayed, that's right.

Testing the Web Service

For testing our first web service, we will use SoapUI web service client.
In SoapUI :
  • Select the menu File → New WSDL Project
  • Specify the following informations :
    • Project Name : HelloWorld
    • Initial WSDL : http://localhost:8080/HelloWorld/helloWorld?wsdl
  • Click on OK button : a default request is created.

Now you can put your name in the SOAP envelope and call the web service !