Message Endpoints
Using web services for RPC style communication is what all the other wiki documentation is about. However, this is
not where web services is going. After all, why would you use something as complex to setup and deploy
as a web service if you only what to make a remote procedure call. The massive serialization overhead imposed
by SOAP only adds pain to injury - your RPC will be slow. If you can't use RMI because of firewall restrictions
and you want your components to communicate via HTTP, use the HTTP invoker not web services. Among the few valid
reasons to use RPC style web services is that you have an interoperability issue with some other that requires you
to do so.
So what is the hype about web services in Java to Java communication if it only adds complexity and is slower
than traditional RPC? The answer is document style web services.
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.
Here is an example:
<env:Envelope> <env:Body> <ns1:Order xmlns:ns1='http://ws.sample'> <Customer>Kermit</Customer> <Item>Ferrari</Item> </ns1:Order> </env:Body> </env:Envelope>
The complex type for the Order element must be definied in schema and there must be a Java bean that represents
the order. What the endpoint method is called is irrelevant, important is that there is a method that takes a single
OrderBean parameter. The mapping between the Order element and the OrderBean is definied in
jaxrpc-mapping.xml.
So far so good, but how about routing messages to an endpoint where the message structure is unknown? Lets use a generic service endpoint interface like this:
public interface MessageEndpoint extends Remote { public Element processElement(Element msg) throws RemoteException; }
There is no defined schema mapping for javax.xml.dom.Element in the JAX-RPC specification. The schema type that probably makes most sense is xsd:anyType. Therefore the WSDL that describes the service could look like this:
<definitions ...> <types> <schema ...> <element name="Order" type="xsd:anyType"></element> <element name="Response" type="xsd:anyType"></element> </schema> </types> <message name="MessageEndpoint_processElement"> <part name="order" element="tns:Order"></part> </message> <message name="MessageEndpoint_processElementResponse"> <part name="result" element="tns:Response"></part> </message> <portType name="MessageEndpoint"> <operation name="processElement" parameterOrder="order"> <input message="tns:MessageEndpoint_processElement"/> <output message="tns:MessageEndpoint_processElementResponse"></output> </operation> </portType> <binding name="MessageEndpointBinding" type="tns:MessageEndpoint"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"></soap:binding> <operation name="processElement"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal" namespace="http://ws.sample"></soap:body> </input> <output> <soap:body use="literal" namespace="http://ws.sample"></soap:body> </output> </operation> </binding> <service name="MessageEndpointService"> <port name="MessageEndpointPort" binding="tns:MessageEndpointBinding"> <soap:address location="REPLACE_WITH_ACTUAL_URL"></soap:address> </port> </service> </definitions>
and the corresponding java mapping file could look like this:
<java-wsdl-mapping ...> <package-mapping> <package-type>org.jboss.test.webservice.message</package-type> <namespaceURI>http://ws.sample</namespaceURI> </package-mapping> <service-interface-mapping> <service-interface>org.jboss.test.webservice.message.MessageEndpointService</service-interface> <wsdl-service-name xmlns:serviceNS="http://ws.sample">serviceNS:MessageEndpointService</wsdl-service-name> <port-mapping> <port-name>MessageEndpointPort</port-name> <java-port-name>MessageEndpointPort</java-port-name> </port-mapping> </service-interface-mapping> <service-endpoint-interface-mapping> <service-endpoint-interface>org.jboss.test.webservice.message.MessageEndpoint</service-endpoint-interface> <wsdl-port-type xmlns:portTypeNS="http://ws.sample">portTypeNS:MessageEndpoint</wsdl-port-type> <wsdl-binding xmlns:bindingNS="http://ws.sample">bindingNS:MessageEndpointBinding</wsdl-binding> <service-endpoint-method-mapping> <java-method-name>processElement</java-method-name> <wsdl-operation>processElement</wsdl-operation> <method-param-parts-mapping> <param-position>0</param-position> <param-type>org.w3c.dom.Element</param-type> <wsdl-message-mapping> <wsdl-message xmlns:wsdlMsgNS="http://ws.sample">wsdlMsgNS:MessageEndpoint_processElement</wsdl-message> <wsdl-message-part-name>Order</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <wsdl-return-value-mapping> <method-return-value>org.w3c.dom.Element</method-return-value> <wsdl-message xmlns:wsdlMsgNS="http://ws.sample">wsdlMsgNS:MessageEndpoint_processElementResponse</wsdl-message> <wsdl-message-part-name>Response</wsdl-message-part-name> </wsdl-return-value-mapping> </service-endpoint-method-mapping> </service-endpoint-interface-mapping> </java-wsdl-mapping>
The service endpoint implementation will receive a SOAPElement that represents the order message. To work with that message, you would use the SAAJ API, simmilar to this
public Element processElement(Element msg) throws RemoteException { SOAPElement soapElement = (SOAPElement)msg; SOAPFactory factory = SOAPFactory.newInstance(); Name name = factory.createName("Order", PREFIX, NAMESPACE_URI); Name elementName = soapElement.getElementName(); if (name.equals(elementName) == false) throw new IllegalArgumentException("Unexpected element: " + elementName); name = factory.createName("Customer"); soapElement = (SOAPElement)soapElement.getChildElements(name).next(); String elementValue = soapElement.getValue(); if ("Customer".equals(soapElement.getLocalName()) && "Kermit".equals(elementValue) == false) throw new IllegalArgumentException("Unexpected element value: " + elementValue); soapElement = (SOAPElement)soapElement.getNextSibling(); elementValue = soapElement.getValue(); if ("Item".equals(soapElement.getLocalName()) && "Ferrari".equals(elementValue) == false) throw new IllegalArgumentException("Unexpected element value: " + elementValue); // Setup document builder DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); // Prepare response DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); Document doc = builder.parse(new ByteArrayInputStream(response.getBytes())); return doc.getDocumentElement(); }
Note: Even though element parameters are instances of javax.xml.soap.SOAPElement (as indicated by the cast above example), don't try to map to this type in the jaxrpc mapping file. Just map to org.w3c.dom.Element and then cast it.
Comments