Providing a document style service endpoint
Imagine a world, where two business partners agree on the exchange of complex business documents
that are well defined in XML schema. You send me a document describing your purchase order,
I respond (immediately or later) with a document that describes the status of your purchase
order. No need to agree on such low level details as operation names and their associated
parameters.
The technology that is used for exchange of these complex documents is document/literal web
services. The payload of the SOAP message is an XML document that can be validated against XML
schema. No wrapping RPC element, no individual parameters.
The service endpoint interface
Let's take a more complex SEI than in the previous example:
public interface OrderProcess extends Remote { OrderResponse processOrder(OrderItem[] items, Person person) throws RemoteException, OrderException; }
All parameter and return types are complex java beans and we even use an array of them as an input parameter.
Generating required deployment artifacts
wscompile -cp output/classes -gen:server -f:documentliteral -mapping jaxrpc-mapping.xml -keep config.xml
This time we use -f:documentliteral and the -keep option to instruct wscompile to keep the generated java sources.
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="OrderProcessService" targetNamespace="http://org.jboss.test.webservice/samples2" typeNamespace="http://org.jboss.test.webservice/samples2/types" packageName="org.jboss.test.webservice.samples2"> <interface name="org.jboss.test.webservice.samples2.OrderProcess"></interface> </service> </configuration>
The Web Service Description
The WSDL for our SEI is shown next
<definitions name="OrderProcessService" targetNamespace="http://org.jboss.test.webservice/samples2" xmlns:tns="http://org.jboss.test.webservice/samples2" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:ns2="http://org.jboss.test.webservice/samples2/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> <types> <schema targetNamespace="http://org.jboss.test.webservice/samples2/types" xmlns:tns="http://org.jboss.test.webservice/samples2/types" xmlns:soap11-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://www.w3.org/2001/XMLSchema"> <complexType name="processOrder"> <sequence> <element name="arrayOfOrderItem_1" type="tns:OrderItem" nillable="true" minOccurs="0" maxOccurs="unbounded"></element> <element name="Person_2" type="tns:Person" nillable="true"></element> </sequence> </complexType> <complexType name="OrderItem"> <sequence> <element name="name" type="string" nillable="true"></element> <element name="quantity" type="int"></element> </sequence> </complexType> <complexType name="Person"> <sequence> <element name="age" type="int"></element> <element name="name" type="string" nillable="true"></element> </sequence> </complexType> <complexType name="processOrderResponse"> <sequence> <element name="result" type="tns:OrderResponse" nillable="true"></element> </sequence> </complexType> <complexType name="OrderResponse"> <sequence> <element name="items" type="tns:OrderItem" nillable="true" minOccurs="0" maxOccurs="unbounded"></element> <element name="message" type="string" nillable="true"></element> </sequence> </complexType> <complexType name="OrderException"> <sequence> <element name="message" type="string" nillable="true"></element> </sequence> </complexType> <element name="processOrder" type="tns:processOrder"></element> <element name="processOrderResponse" type="tns:processOrderResponse"></element> <element name="OrderException" type="tns:OrderException"></element> </schema> </types> <message name="OrderProcess_processOrder"> <part name="parameters" element="ns2:processOrder"></part> </message> <message name="OrderProcess_processOrderResponse"> <part name="result" element="ns2:processOrderResponse"></part> </message> <message name="OrderException"> <part name="OrderException" element="ns2:OrderException"></part> </message> <portType name="OrderProcess"> <operation name="processOrder"> <input message="tns:OrderProcess_processOrder"/> <output message="tns:OrderProcess_processOrderResponse"></output> <fault name="OrderException" message="tns:OrderException"></fault> </operation> </portType> <binding name="OrderProcessBinding" type="tns:OrderProcess"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"></soap:binding> <operation name="processOrder"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal"></soap:body> </input> <output> <soap:body use="literal"></soap:body> </output> <fault name="OrderException"> <soap:fault name="OrderException" use="literal"></soap:fault> </fault> </operation> </binding> <service name="OrderProcessService"> <port name="OrderProcessPort" binding="tns:OrderProcessBinding"> <soap:address location="REPLACE_WITH_ACTUAL_URL"></soap:address> </port> </service> </definitions>
There are a few things to notice about this wsdl. First and most importantly the messages involved in the
operation only use a single part and that part is defined by an element definition in the schema. That
means the entire payload of the service is defined in schema. There is no wrapping RPC element and no
individual parameters. The same is true for the response message.
Also note, that the XML element name of the incoming message is the same as the operation name.
This is a convenient convention and not yet required by spec.
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.samples2.OrderProcess_processOrder_RequestStruct</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.samples2.OrderProcess_processOrder_ResponseStruct</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>
Generated request/response structure objects
wscompile generated the request/response structures and we look at them next. First the request structure
public class OrderProcess_processOrder_RequestStruct { protected org.jboss.test.webservice.samples2.OrderItem[] arrayOfOrderItem_1; protected org.jboss.test.webservice.samples2.Person Person_2; public OrderProcess_processOrder_RequestStruct() { } public OrderProcess_processOrder_RequestStruct(org.jboss.test.webservice.samples2.OrderItem[] arrayOfOrderItem_1, org.jboss.test.webservice.samples2.Person Person_2) { this.arrayOfOrderItem_1 = arrayOfOrderItem_1; this.Person_2 = Person_2; } public org.jboss.test.webservice.samples2.OrderItem[] getArrayOfOrderItem_1() { return arrayOfOrderItem_1; } public void setArrayOfOrderItem_1(org.jboss.test.webservice.samples2.OrderItem[] arrayOfOrderItem_1) { this.arrayOfOrderItem_1 = arrayOfOrderItem_1; } public org.jboss.test.webservice.samples2.Person getPerson_2() { return Person_2; } public void setPerson_2(org.jboss.test.webservice.samples2.Person Person_2) { this.Person_2 = Person_2; } }
and now the response structure
public class OrderProcess_processOrder_ResponseStruct { protected org.jboss.test.webservice.samples2.OrderResponse result; public OrderProcess_processOrder_ResponseStruct() { } public OrderProcess_processOrder_ResponseStruct(org.jboss.test.webservice.samples2.OrderResponse result) { this.result = result; } public org.jboss.test.webservice.samples2.OrderResponse getResult() { return result; } public void setResult(org.jboss.test.webservice.samples2.OrderResponse result) { this.result = result; } }
You see, that they nicely wrap the input/return parameters from the SEI.
The endpoint as web application
A java service endpoint is deployed as a web application. Therefore we need a web.xml deployment descriptor, which
we will have to code by hand.
<web-app 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/web-app_2_4.xsd" version="2.4"> <servlet> <servlet-name>OrderProcess</servlet-name> <servlet-class>org.jboss.test.webservice.samples2.OrderProcessImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>OrderProcess</servlet-name> <url-pattern>/OrderProcess</url-pattern> </servlet-mapping> </web-app>
Note, that we declare the endpoint implemenation bean in the <servlet-class> element, but it is actually not
a servlet at all.
The webservices deployment descriptor
One more descriptor is needed that glues everything together. It is called webservices.xml and must also be coded
by hand. Here it is
<webservices 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://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1"> <webservice-description> <webservice-description-name>OrderProcess</webservice-description-name> <wsdl-file>WEB-INF/wsdl/OrderProcessService.wsdl</wsdl-file> <jaxrpc-mapping-file>WEB-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>PortComponent</port-component-name> <wsdl-port>OrderProcessPort</wsdl-port> <service-endpoint-interface>org.jboss.test.webservice.samples2.OrderProcess</service-endpoint-interface> <service-impl-bean> <servlet-link>OrderProcess</servlet-link> </service-impl-bean> </port-component> </webservice-description> </webservices>
Clients connecting to document style endpoints
Although there is better support on the client side for wrapping/unwrapping document style messages.
I would recommend using a simmilar technique and using the generated request/response structures on the client
as well.
/** Test a valid access */ public void testValidAccess() throws Exception { InitialContext iniCtx = getClientContext(); Service service = (Service)iniCtx.lookup("java:comp/env/service/OrderProcess"); OrderProcess endpoint = (OrderProcess)service.getPort(OrderProcess.class); Person p = new Person("Tom", 3); 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, res.getItems()[0]); assertEquals(i1, res.getItems()[1]); assertEquals(i2, res.getItems()[2]); assertEquals("approved", res.getMessage()); } /** Test a invalid access */ public void testNullPerson() throws Exception { InitialContext iniCtx = getClientContext(); Service service = (Service)iniCtx.lookup("java:comp/env/service/OrderProcess"); OrderProcess endpoint = (OrderProcess)service.getPort(OrderProcess.class); OrderItem i1 = new OrderItem("Ferrari", 1); OrderItem i2 = new OrderItem("Twix", 10); OrderItem i3 = new OrderItem("IceCream", 3); try { endpoint.processOrder(new OrderItem[] { i1, i2, i3 }, null); fail("OrderException expected"); } catch (OrderException e) { // ignore expected exception } }
Comments