Client access of a Document style service endpoint
In this tutorial we demonstrate how to create a WS4EE compliant web service client accessing a remote document style
service endpoint.
Again, we use a J2EE client application to connect to the service we developed in Document style service endpoint. In real life you are more likely to connect to some web service not deployed
on JBoss, probably not even running on Java.
Obtaining the remote WSDL
The WSDL is often the starting point for a web service client. You normally obtain it from some URL like this
http://TDDELL:8080/ws4ee-samples2-doc/OrderProcess?wsdl
The WSDL returned from the URL is the one we generated with wscompile and in the previous tutorial. There is one important difference however, the service element contains the proper location of the service
<service name="OrderProcessService"> <port binding="tns:OrderProcessBinding" name="OrderProcessPort"> <soap:address location="http://TDDELL:8080/ws4ee-samples2-doc/OrderProcess"></soap:address> </port> </service>
Generating the required deployment artifacts
From the WSDL we can generate the service endpoint interface (SEI), the service interface (SI), the Java/XML mapping descriptor (jaxrpc-mapping.xml) and possibly the complex types that are used in the SEI. Again we use wscompile to do that.
wscompile -gen:client -f:documentliteral -f:wsi -mapping jaxrpc-mapping-client.xml -keep config-client.xml
wscompile needs a configuration file for its operation, we use
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <wsdl location="http://localhost:8080/ws4ee-samples2-doc/OrderProcess?wsdl" packageName="org.jboss.test.webservice.samples2docclient"> </wsdl> </configuration>
Having read about Document style service endpoint you know that we prefer to wrap all
request/response parameters in a single java object that represents the entire SOAP payload (i.e. the SOAP body element). The -f:wsi feature instructs wscompile to do that.
The options to wscompile are described as part
of the jaxrpc documentation that comes with the JWSDP.
The generated service endpoint interface (SEI)
First let's have a look at the generated SEI
public interface OrderProcess extends java.rmi.Remote { public org.jboss.test.webservice.samples2docclient.ProcessOrderResponse processOrder(org.jboss.test.webservice.samples2docclient.ProcessOrder parameters) throws org.jboss.test.webservice.samples2docclient.OrderException, java.rmi.RemoteException; }
We notice that wscompile uses a different naming schema for the request/respose structure. What was called
OrderProcess_proccessOrder_RequestStruct is now called ProcessOrder and what was called
OrderProcess_proccessOrder_ResponseStruct is now called ProcessOrderResponse .
It does not really matter, the generated objects are semantically equivalent.
public class ProcessOrder { protected org.jboss.test.webservice.samples2docclient.OrderItem[] arrayOfOrderItem_1; protected org.jboss.test.webservice.samples2docclient.Person person_2; public ProcessOrder() { } public ProcessOrder(org.jboss.test.webservice.samples2docclient.OrderItem[] arrayOfOrderItem_1, org.jboss.test.webservice.samples2docclient.Person person_2) { this.arrayOfOrderItem_1 = arrayOfOrderItem_1; this.person_2 = person_2; } public org.jboss.test.webservice.samples2docclient.OrderItem[] getArrayOfOrderItem_1() { return arrayOfOrderItem_1; } public void setArrayOfOrderItem_1(org.jboss.test.webservice.samples2docclient.OrderItem[] arrayOfOrderItem_1) { this.arrayOfOrderItem_1 = arrayOfOrderItem_1; } public org.jboss.test.webservice.samples2docclient.Person getPerson_2() { return person_2; } public void setPerson_2(org.jboss.test.webservice.samples2docclient.Person person_2) { this.person_2 = person_2; } } public class ProcessOrderResponse { protected org.jboss.test.webservice.samples2docclient.OrderResponse result; public ProcessOrderResponse() { } public ProcessOrderResponse(org.jboss.test.webservice.samples2docclient.OrderResponse result) { this.result = result; } public org.jboss.test.webservice.samples2docclient.OrderResponse getResult() { return result; } public void setResult(org.jboss.test.webservice.samples2docclient.OrderResponse result) { this.result = result; } }
The generated service interface (SI)
This interface is later bound into JNDI and the client uses it to obtain a dynamic proxy to the (SEI). It looks like this
public interface OrderProcessService extends javax.xml.rpc.Service { public org.jboss.test.webservice.samples2docclient.OrderProcess getOrderProcessPort() throws ServiceException; }
The SI is optional because the client might choose to lookup an instance of the less typed javax.xml.rpc.Service
from JNDI. We see how this is done in a bit.
The generated complex types
The generated complex types are
OrderException
OrderItem
Person
all of which are defined by the schema that was contained in the WSDL. For readability reasons I'll only show the generated exception that maps to the operation fault message part.
public class OrderException extends Exception { private java.lang.String message; public OrderException(java.lang.String message) { super(message); this.message = message; } public java.lang.String getMessage() { return message; } }
Java to XML mapping
Let's have a look how the mapping of the request/response structures are defined in jaxrpc-mapping.xml. For readability resons, I won't show the entire mapping file.
<java-xml-type-mapping> <java-type>org.jboss.test.webservice.samples2docclient.ProcessOrder</java-type> <root-type-qname xmlns:typeNS="http://org.jboss.test.webservice/samples2/types">typeNS:processOrder</root-type-qname> <qname-scope>complexType</qname-scope> <variable-mapping> <java-variable-name>arrayOfOrderItem_1</java-variable-name> <xml-element-name>arrayOfOrderItem_1</xml-element-name> </variable-mapping> <variable-mapping> <java-variable-name>person_2</java-variable-name> <xml-element-name>Person_2</xml-element-name> </variable-mapping> </java-xml-type-mapping> <java-xml-type-mapping> <java-type>org.jboss.test.webservice.samples2docclient.ProcessOrderResponse</java-type> <root-type-qname xmlns:typeNS="http://org.jboss.test.webservice/samples2/types">typeNS:processOrderResponse</root-type-qname> <qname-scope>complexType</qname-scope> <variable-mapping> <java-variable-name>result</java-variable-name> <xml-element-name>result</xml-element-name> </variable-mapping> </java-xml-type-mapping>
What about all the other stuff
wscompile generates a lot of other java files that are not relevant to WS4EE. If you opt to use JWSDP as your
WS client, then you would need these proprietary implementations. But remember, WS4EE is about portable J2EE
compliant web services. We should not need implementation specific artifacts as this would render our WS client unportable to other J2EE compliant app servers.
The client deployment descriptor
An application client uses a descriptor called application-client.xml. What is described here, equally applies
to ejb-jar.xml if an EJB is the WS client or web.xml if a servlet/jsp is the WS client.
<application-client xmlns="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/application-client_1_4.xsd" version="1.4"> <display-name>webservice client app</display-name> <service-ref> <service-ref-name>service/OrderProcess</service-ref-name> <service-interface>org.jboss.test.webservice.samples2docclient.OrderProcessService</service-interface> <wsdl-file>USE_JBOSS_CLIENT_XML_OVERRIDE</wsdl-file> <jaxrpc-mapping-file>META-INF/jaxrpc-mapping-client.xml</jaxrpc-mapping-file> </service-ref> </application-client>
The <service-ref-name> is the JNDI name that the client uses to lookup the SI from it's environment context.
jboss-client.xml deployment descriptor
Here we use a feature where it is possible to specify the URL to the remote WSDL in jboss-client.xml,
rather than packaging the WSDL with the client deployment
<!DOCTYPE jboss-client PUBLIC "-//JBoss//DTD Application Client 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd"> <jboss-client> <jndi-name>ws4ee-client</jndi-name> <service-ref> <service-ref-name>service/OrderProcess</service-ref-name> <wsdl-override>http://localhost:8080/ws4ee-samples2-doc/OrderProcess?wsdl</wsdl-override> </service-ref> </jboss-client>
Accessing the remote web service
First the client gets an implementation of the SI from JNDI, from that it gets an implementation of the SEI.
Then it uses the proxy that implements the SEI like a local java object.
/** Test a valid access */ public void testValidAccess() throws Exception { InitialContext iniCtx = getInitialContext(); OrderProcessService service = (OrderProcessService)iniCtx.lookup("java:comp/env/service/OrderProcess"); OrderProcess endpoint = service.getOrderProcessPort(); Person p = new Person(3, "Tom"); OrderItem i0 = new OrderItem("Ferrari", 1); OrderItem i1 = new OrderItem("Twix", 10); OrderItem i2 = new OrderItem("IceCream", 3); OrderResponse res = endpoint.processOrder(new OrderItem[]{i0, i1, i2}, p); assertEquals(3, res.getItems().length); assertEquals(i0.getName(), res.getItems()[0].getName()); assertEquals(i0.getQuantity(), res.getItems()[0].getQuantity()); assertEquals(i1.getName(), res.getItems()[1].getName()); assertEquals(i1.getQuantity(), res.getItems()[1].getQuantity()); assertEquals(i2.getName(), res.getItems()[2].getName()); assertEquals(i2.getQuantity(), res.getItems()[2].getQuantity()); assertEquals("aproved", res.getMessage()); }
Specifing the JNDI properties
Again specific to J2EE application clients, you need to set the j2ee.clientName property, like this
/** * Get the client's env context, see source forge tracker [840598] for details */ protected InitialContext getInitialContext() throws NamingException { Properties env = new Properties(); env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); env.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client"); env.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099"); env.setProperty("j2ee.clientName", "ws4ee-client"); return new InitialContext(env); }
Comments