Before moving on to the rest of the
examples, we need to make a quick note about Apache
SOAP’s routing and service capability.
It’s a concept that is likely to be applicable to
other SOAP infrastructures you may use in the future. If you have
poked around with Apache (or you looked ahead at the rest of the
chapter), you may have noticed that many samples use a common URL,
which either looks like
http://localhost:8080/soap/servlet/messagerouter
or http://localhost:8080/soap/servlet/rpcrouter
.
These special URLs point to Apache’s routing and
dispatching mechanism. This mechanism looks at the content of the
SOAP envelope and decides which class to load and which method to
call within that class. Apache refers to this destination as a
service
.
The service is registered with the servlet engine in a two-step
process. First, an XML deployment descriptor is created, specifying
details about the class name of the service, its associated method
call, and the target URI. Then a special
org.apache.soap.server.ServiceManagerClient
class
is invoked to register the service with Apache SOAP.
Using the RPC router, any Java class and method can be registered as a service; the Apache SOAP infrastructure will call the method with the appropriate parameters. An example of SOAP-RPC can be found in Chapter 4. Using the message router, the method name is the tag name of the body entry in the SOAP envelope, and the method always conforms to the following signature:
public void anyMessageMethod
(Envelope requestEnvelope,
SOAPContext requestContext, SOAPContext responseContext)
for which anyMessageMethod
is specified in
the deployment descriptor for the service and is also the tag name of
the body entry in the SOAP envelope.
Sending
SOAP messages to the SimpleHTTPReceive
servlet is
an excellent way of dumping raw output to the screen. A more
convenient way to see the raw output from the sender and the receiver
is to use the TunnelGui included with Apache SOAP. TunnelGui is a
simple utility that intercepts the HTTP request, displays it in a
window on the screen, and forwards the request to the ultimate
destination. It’s a great tool for analyzing and
debugging problems that have to do with unexpected output. To launch
the Apache TunnelGui utility, issue the following command:
java org.apache.soap.util.net.TcpTunnelGui 5555 localhost 8080
5555
is the port on which TunnelGui listens;
8080
is the port to which it forwards requests
(8080
also happens to be the default port for
Tomcat). To send a SOAP request through
TunnelGui, specify port 5555
instead of
8080
in the destination URL. For example, issue
the following command:
java GenericHTTPSoapClient -df ./PO.xml -url
http://localhost:5555/examples/servlet/SimpleHTTPReceive
This command displays the expected output in the Tomcat server window and shows the request and response in the TunnelGui window (Figure 3-2).
In the previous example, we promised to show another way to set up a servlet that passes the SOAP envelope directly to the receiver. To do this, we’ll use Apache’s message router service. For this example, we can use the GenericHTTPClient without any modifications; simply override the destination URL on the command line:
java GenericHTTPSoapClient -url http://localhost:8080/soap/servlet/messagerouter
Here’s what you should see in the command window for the sender:
_________________________________________________________ Starting GenericHTTPSoapClient: host url = http://localhost:8080/soap/servlet/messagerouter data file = ./PO.xml _________________________________________________________ Sent SOAP Message with Apache HTTP SOAP Client. Waiting for response.... <PurchaseOrderResponse>Accepted</PurchaseOrderResponse>
In the Tomcat console window, you should see:
Received a PurchaseOrder!! Header==> <jaws:MessageHeader xmlns:jaws="urn:oreilly:jaws:samples"><From>Me</From><To>You </To><MessageId>9999</MessageId></jaws:MessageHeader> Body====> <PurchaseOrder xmlns="urn:oreilly-jaws-samples"> <shipTo country="US"> <name>Joe Smith</name> <street>14 Oak Park</street> <city>Bedford</city> <state>MA</state> <zip>01730</zip> </shipTo> <items> <item partNum="872-AA"> <productName>Candy Canes</productName> <quantity>444</quantity> <price>1.68</price> <comment>I want candy!</comment> </item> </items> </PurchaseOrder>
This example works partly because of the information in the
deployment descriptor for this service. The class name of the
receiver is PurchaseOrderAcceptor
. The method name
is PurchaseOrder
, which is also the name of the
main tag in the SOAP body. Here’s the deployment
descriptor:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:oreilly-jaws-samples" type="message"> <isd:provider type="java" scope="Application" methods="PurchaseOrder PurchaseOrderWithAttachment"> <isd:java class="PurchaseOrderAcceptor"/> </isd:provider> <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener> </isd:service>
If you are going to experiment with creating your own Apache
services, remember that the URI represented by the
id
attribute of the service tag in the deployment
descriptor must match the target URI used in the
Message.send( )
call—something
that’s not obvious from reading the Apache
documentation.
Here’s a listing of the
PurchaseOrderAcceptor
class and its
PurchaseOrder( )
method. It uses the Envelope,
Header, and Body APIs to pick apart the header and body of the
message:
import org.apache.soap.Envelope; import org.apache.soap.Constants; import org.apache.soap.SOAPException; import org.apache.soap.rpc.SOAPContext; public class PurchaseOrderAcceptor { public void PurchaseOrder(Envelope requestEnvelope, SOAPContext requestContext, SOAPContext responseContext) throws SOAPException { System.out.println("Received a PurchaseOrder!!"); java.io.StringWriter writer = new java.io.StringWriter( ); org.apache.soap.Header header = requestEnvelope.getHeader( ); java.util.Vector headerEntries = header.getHeaderEntries( ); writer.write(" Header==> "); for (java.util.Enumeration e = headerEntries.elements(); e.hasMoreElements( );) { org.w3c.dom.Element el = (org.w3c.dom.Element)e.nextElement( ); org.apache.soap.util.xml.DOM2Writer.serializeAsXML( (org.w3c.dom.Node)el, writer); } org.apache.soap.Body body = requestEnvelope.getBody( ); java.util.Vector bodyEntries = body.getBodyEntries( ); writer.write(" Body====> "); for (java.util.Enumeration e = bodyEntries.elements(); e.hasMoreElements( );) { org.w3c.dom.Element el = (org.w3c.dom.Element)e.nextElement( ); org.apache.soap.util.xml.DOM2Writer.serializeAsXML( (org.w3c.dom.Node)el, writer); } System.out.println(writer.toString( )); try { //should really be better XML with declaration and namespaces responseContext.setRootPart( "<PurchaseOrderResponse>Accepted</PurchaseOrderResponse>", "text/xml"); } catch(Exception e) { throw new SOAPException(Constants.FAULT_CODE_SERVER, "Error writing response", e); } } }