In Section 6.6, earlier in this chapter, you saw that for both RPC- and document-style operations JAX-RPC method call arguments and return values are placed in the SOAP message body. However, SOAP extensions and higher-level business frameworks based on SOAP messaging, such as WS-Routing and ebXML, typically add elements to the SOAP header as well as or instead of using the message body. The WS-Security extension, for example, provides a framework to include in the SOAP header authentication information, and provides security tokens (such as X.509 certificates) that can be used to verify the identity of the message sender or protect against modification of the message while it is in transit. Applications built using the SAAJ API have direct access to the SOAP header and can therefore add any necessary elements to it before a message is transmitted as well as process header blocks when a message is received. A JAX-RPC application can also access the SOAP header using one (or both) of two techniques that are the subject of this section � mapping header content to method arguments or creating a SOAP message handler.
Mapping header content to method arguments is a natural and convenient way for a JAX-RPC application to access the header because it is consistent with the JAX-RPC programming model and avoids the need for application code to have any direct dependency on the structure of the underlying messages. You can use this technique provided that the following conditions are satisfied:
The service is described by a WSDL document rather than a Java interface definition.
The message parts that are to be included in the SOAP header are described using soap:header elements in the binding section of the WSDL document.
To illustrate the mapping of header blocks to method arguments, we'll create a web service based on the one used to demonstrate the dynamic invocation interface earlier in this chapter (the definition of which you'll find in Example 6-5). However, we'll add to it a header element that contains authentication information for the user invoking the service. The authentication information will be inserted by the client application and extracted and interpreted by the web service implementation. This example provides a very simplistic demonstration of how you can use the JAX-RPC APIs to provide application-level security instead of relying on the web container to authenticate the calling user, as shown in the previous section. The element that will be added to the header is shown in Example 6-37.
<auth> <UserName>JWSUserName</UserName> <Password>JWSPassword</Password> </auth>
In a real application, you would want to encrypt some or all of this information to protect it from unauthorized disclosure, and you would also need to include some kind of security token that would guarantee that the authentication information belongs to and was provided by the message sender. In order to keep this example simple, we're not going to attempt either of these things. If you'd like to investigate how this might be done, the WS-Security specification, originated by IBM, Microsoft, and Verisign, defines SOAP header elements and procedures that make it possible to securely incorporate authentication details and other sensitive information in a SOAP message. The specification can be downloaded from http://www.verisign.com/wss/wss.pdf.
In order to handle header information through JAX-RPC-generated method calls, you need to have a WSDL definition of the service. Such a definition was created during the deployment of the service whose original Java interface definition is shown in Example 6-5. For the purposes of this example, we'll take a copy of the WSDL document for that service and add to it the elements required to send authentication information to the server whenever the client application invokes any of the service endpoint interface methods. We'll also arrange for an element containing the date and time at which the service processed the request to be returned to the client to be included.
The first step is to add to the WSDL file a definition to the types section for the authentication information, as shown in Example 6-38.
<types> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" targetNamespace= "urn:jwsnut.chapter6.headerbookservice/types/HeaderBookQuery"> <!-- Header element for user name and password --> <complexType name="Authentication"> <sequence> <element name="UserName" type="xsd:string"/> <element name="Password" type="xsd:string"/> </sequence> </complexType> </schema> </types>
Next, you need to arrange for an instance of this type to be included in the header of the SOAP messages generated for each operation defined by this service. Recall from Chapter 5 that the WSDL definition for a web service describes the content of the SOAP message sent by the client application for a particular operation as follows:
In the portType section, the operation element for the operation specifies an input message made up of zero or more parts, whose types are defined in the types section or imported from an external schema.
Within the binding section, there is a corresponding operation element that maps these parts to the SOAP message body, to the header, or to an attachment, as appropriate.
Here, for example, are the relevant parts of the WSDL definition for the getBookTitle operation, which requires an integer argument:
<!-- Input message definition --> <message name="SmallBookQuery_getBookTitle"> <part name="int_1" type="xsd:int"/> </message> <!-- Subset of complete portType definition --> <portType name="SmallBookQuery"> <operation name="getBookTitle" parameterOrder="int_1"> <input message="tns:SmallBookQuery_getBookTitle"/> <output message="tns:SmallBookQuery_getBookTitleResponse"/> </operation> </portType> <!-- Subset of complete binding definition --> <binding name="SmallBookQueryBinding" type="tns:SmallBookQuery"> <operation name="getBookTitle"> <soap:operation soapAction=""/> <input> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:jwsnut.chapter6.smallbookservice/wsdl/ SmallBookQuery"/> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:jwsnut.chapter6.smallbookservice/wsdl/ SmallBookQuery"/> </output> </operation> </binding>
The result of this definition is that the integer argument defined by the part labeled int_1 is placed in the body of the SOAP message, wrapped in the usual way with an element named for the operation. In order to arrange for an additional element to be added to the header, you need to define that element as a part and then reference it from a soap:header element within the operation definition in the binding section. Since the same header information is going to be included in each SOAP request message, instead of adding the part to each individual message element, we create a new message element containing only this part, and use the message attribute of the soap:header element to cause it to be included in the SOAP message header.[11] We'll use the same technique to define the part that contains the time at which the server processed the SOAP message, which is placed in the SOAP header of the reply message.
[11] Refer to Section 5.2.6.2 for a description of the soap:header element.
|
The WSDL document that results from these changes can be found in the file chapter6\headerbookservice\Input_HeaderBookQuery.wsdl relative to the example source code for this book. This document defines a new web service containing a service endpoint interface called HeaderBookQuery, whose operations are the same as those of SmallBookQuery, apart from the addition of authentication and timestamp information. Here's how the additional message elements that contain the authentication information and the timestamp are defined:
<message name="HeaderBookQuery_Auth"> <part name="auth" type="typesns:Authentication"/> </message> <message name="HeaderBookQuery_Time"> <part name="time" type="xsd:dateTime"/> </message>
Note that the time element uses a built-in XML Schema type and therefore does not require a new type to be defined in the types section.
Each operation in the binding section is modified to include soap:header elements that refer to these message parts. For example, the modified definition for the getBookTitle operation looks like this:
<operation name="getBookTitle"> <soap:operation soapAction=""/> <input> <soap:header message="tns:HeaderBookQuery_Auth" part="auth" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace= "urn:jwsnut.chapter6.headerbookservice/wsdl/ HeaderBookQuery"/> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:jwsnut.chapter6.headerbookservice/wsdl/ HeaderBookQuery"/> </input> <output> <soap:header message="tns:HeaderBookQuery_Time" part="time" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace= "urn:jwsnut.chapter6.headerbookservice/wsdl/ HeaderBookQuery"/> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:jwsnut.chapter6. headerbookservice/wsdl/HeaderBookQuery"/> </output> </operation>
The input message element now includes a soap:header element that specifies that an instance of the auth part from the HeaderBookQuery_Auth message must be bound to the SOAP message header when the request message is sent. Similarly, the SOAP header for the reply message must contain a time element, as defined by the message element called HeaderBookQuery_Time. The JAX-RPC runtime is responsible for obtaining the value to be used to construct the auth element in the outgoing message and for making the returned timestamp available to the client application when the response is received. These values are conveyed via the arguments of the methods in the Java interface that are created from these WSDL definitions.
Having completed the WSDL definition, before you can write either the service implementation or an application client, you need the corresponding Java interface. As you saw earlier in this chapter, this is generated from the WSDL definition using the wscompile utility by giving it a config.xml file containing a wsdl element. You can create and compile the Java interface definition by opening a command window, making chapter6\headerbookservice your working directory, and typing the command:
ant generate-interface
The wscompile utility does not automatically include method call arguments for parts that are mapped to the SOAP header. To force it to do so, you need to use the -f:explicitcontext option.[12] Here is the command line used by the generate-interface target:
[12] The name explicitcontext is used because when this option is specified, the elements carried in the SOAP header, which typically represent context for the request or response in the body (and here carry security context information) are explicitly exposed in the method signatures of the service endpoint interface. This contrasts with implicit context information�such as the authenticated Principal available from the ServletEndpointContext or the SessionContext�which does not manifest itself in the programming interface of the web service.
wscompile -gen:server -f:explicitcontext -keep -model generated/interface/model -s generated/interface -d output/interface config.xml
The generated files are placed in the directory generated/interface/ora/jwsnut/chapter6/headerbookservice. The type defined in Example 6-38 to hold the authentication information becomes a simple value type called Authentication with attributes that hold a username and password. This value type is used in the signatures of all of the methods in the generated service endpoint interface, which is shown in Example 6-39.
public interface HeaderBookQuery extends java.rmi.Remote { public void log(java.lang.String string_1, Authentication auth, javax.xml.rpc.holders.CalendarHolder time) throws java.rmi.RemoteException; public int getBookCount(Authentication auth, javax.xml.rpc.holders.CalendarHolder time) throws java.rmi.RemoteException; public java.lang.String getBookTitle(int int_1, Authentication auth, javax.xml.rpc.holders.CalendarHolder time) throws java.rmi.RemoteException; public void getBookAuthor(java.lang.String string_1, javax.xml.rpc.holders.StringHolder string_2, Authentication auth, javax.xml.rpc.holders. CalendarHolder time) throws HeaderBookServiceException, java.rmi.RemoteException; }
Notice that the arguments that represent the explicit context, which are highlighted, have been added after the regular method arguments. Note also that the time value that will be returned in the SOAP header of every response message is represented by a CalendarHolder object because it is an output parameter. This, of course, is a special case, since the data type of this value is one of those directly supported by JAX-RPC and for which a predefined Holder object exists. If you define your own type (such as the Authentication type) and assign it as a value to be returned in a response message, then a suitable Holder class is generated by wscompile and included in the method signature.
The most convenient aspect of using explicit context is that application code simply passes the value or values required for the SOAP header as method arguments and does not need to be aware that they are actually being carried in the header. Similarly, values received in a SOAP header become associated with Holder arguments in the usual way. Example 6-40 shows an extract from the example client application for this service.
// Get a reference to the stub. HeaderBookService_Impl service = new HeaderBookService_Impl( ); HeaderBookQuery bookQuery = (HeaderBookQuery)service.getHeaderBookQueryPort( ); // Create the required authentication information String userName = System.getProperty("HeaderBooks.user"); String password = System.getProperty("HeaderBooks.password"); Authentication auth = new Authentication(userName, password); // Get info for each book. StringHolder stringHolder = new StringHolder( ); CalendarHolder calendarHolder = new CalendarHolder( ); int count = bookQuery.getBookCount(auth, calendarHolder); Calendar calendar = (Calendar)calendarHolder.value; System.out.println("Book count = " + count); System.out.println("Processed at: " + calendar.getTime( ));
As you can see, the username and password are obtained from system properties (which are actually set on the command line using values in your jwsnutExamples.properties file) and then used to construct the Authentication object. This object and a CalendarHolder are passed as arguments to the getBookCount( ) method, and the timestamp returned by the service implementation is extracted and printed in the normal way.
The service implementation consists of a servant class that implements the generated HeaderBookQuery interface shown in Example 6-39. Given that the values that represent the explicit context are presented as method arguments, the servant class can retrieve the authentication information that is supplied for each method call from the Authentication argument, as well as return the invocation time in the CalenderHolder supplied to it by the JAX-RPC runtime system. This makes the servant class as transparent to the fact that these values are carried in the SOAP header as the client application is. The implementation of the getBookCount( ) method, for example, is shown in Example 6-41, together with the checkAccess( ) method that it uses to verify the username and password supplied to it. The correct username and password values are obtained from servlet initialization parameters, using the same code as that shown in Example 6-33.
public int getBookCount(Authentication auth, CalendarHolder calendarHolder) { calendarHolder.value = new GregorianCalendar( ); String[] titles = HeaderBookServiceServantData.getBookTitles( ); return titles == null || !checkAccess(auth) ? 0 : titles.length; } /** * Check whether the calling user is authenticated. */ private boolean checkAccess(Authentication auth) { boolean allowed = false; if (userName != null && password != null && auth != null) { // Authentication is configured. return userName.equals(auth.getUserName( )) && password.equals(auth.getPassword( )); } return allowed; }
In order to make this work with the JWSDP, however, you need to be careful when packaging the service implementation. Recall that the ties for the service implementation are generated when you run wsdeploy, which bases its actions on the endpoint definitions in the jaxrpc-ri.xml file. Ordinarily, the ties are generated by introspecting the methods of the Java interface definition in the HeaderBookQuery class, which is passed to it by the interface attribute of the endpoint element. However, without additional information, there is no way to tell which of the method arguments map to body content and which map to the SOAP header. This information was originally supplied in the WSDL document, which cannot be supplied to wsdeploy. In the absence of any information to the contrary, wsdeploy generates ties that expect all of the method arguments to correspond to elements in the SOAP message body, which leads to incorrect behavior in this case.
The solution to this problem is the same one we used in Section 6.6.2.2, earlier in this chapter, where we had to ensure that wsdeploy knew that the methods of a Java endpoint interface should map to document-style operations � we generate a model file and reference it from the endpoint element in the jaxrpc-ri.xml file. This, then is another case in which we genuinely need to include the model file. The model file provides the same information to either j2eec or deploytool, if you are using the J2EE 1.4 reference implementation.
To verify that this example works, you can package and deploy the service by making chapter6\headerbookservice your working directory and typing the command:
ant deploy
Then, build and run the client application by typing:
ant compile ant run-client
The client prints the number of books returned by the getBookCount( ) method followed by the time at which the server performed the operation, as returned in the SOAP header of the reply message. It then prints the title and author of all of the books that the server knows about. This works because when you build and deploy the service implementation, its web.xml file is initialized with the username and password from your jwsnutExamples.properties file as the credentials for the user to be allowed to access the service. When you run the client, it extracts the same values and uses them to populate the Authentication object. If you temporarily change the username or password in your jwsnutExamples.properties file and run the client again (without redeploying the service), the values in the Authentication object sent by the client do not match the username and password known to the server. As a result, the getBookCount( ) method returns 0 and you do not see any books listed.
Mapping SOAP header content to method arguments is a simple and convenient way to expose that content without introducing the complexities of SOAP message handling into application-level code. It is, therefore, a useful technique to use in cases where information that forms part of the service endpoint interface definition happens to be carried in the SOAP header. It is, however, not the only means available to the JAX-RPC developer for getting at header content, although it is by far the easiest to use.
Another way to process headers is to access the underlying SOAP message directly. This option is open to any service implementation class that implements the ServiceLifecycle interface, or to a web service implemented as a stateless session bean. It requires the service to use the MessageContext object that can be obtained by calling the getMessageContext( ) method of the ServletEndpointContext in the case of a servlet-hosted web service, or by calling the getMessageContext( ) method of SessionContext for a session bean. In the case of SOAP messaging, the MessageContext is actually an instance of the derived interface javax.xml.rpc.handler.soap.SOAPMessageContext, which provides a method that returns the SOAP message currently being handled:
public SOAPMessage getMessage( )
This method can be called whenever the SOAPMessageContext is valid � that is, from within any of the methods of the servant that implement the service endpoint interface. Once you have a reference to the message, you can use the SAAJ API to access any part of it. To process headers, for example, you might use code like this:
SOAPMessageContext ctx = (SOAPMessageContext)endpointContext.getMessageContext( ); SOAPMessage message = ctx.getMessage( ); SOAPHeader header = message.getSOAPPart().getEnvelope( ).getHeader( ); if (header != null) { // Process the header... }
This technique is very useful, but it has at least two drawbacks:
It can only be used within the web service implementation class. There is nothing corresponding to ServletEndpointContext that would allow a client application to process the SOAP message received as the reply to its request.
It results in low-level logic appearing in what should really be application-level code.
One possible way to make use of message-level access is to compute a digital signature for some or all of the content of a request message, and then place it into a SOAP header before it is transmitted from the client. The header is extracted when the message is received by the server and the signature is verified. Using a digital signature in this way ensures that the part of the message to which the signature applies is not modified while in transit. Obviously, as an application developer, you wouldn't want to be concerned with the details of digital signature algorithms, and you certainly wouldn't want to have to introduce code that intercepts SOAP messages and handles digital signatures into what should really be business logic. Instead, it is more convenient to be able to implement this code separately, outside of the application itself, and have it operate on the messages that the JAX-RPC runtime creates in response to method calls on the service endpoint interface. This capability is provided by JAX-RPC message handlers.
A message handler is a class that can receive and possibly act upon a SOAP message. Depending on what the handler is supposed to do, it might modify the message, log its content somewhere, ignore it, or deem it to be invalid and initiate some form of error handling, such as the generation of a SOAP fault. Message handlers are grouped into a handler chain, which may be inserted into the processing path for a specific service endpoint on the client side, the server side, or both. Figure 6-5 shows a handler chain installed on the client side of a web service.
The chain in this instance consists of three handlers labeled A, B, and C. As indicated in the diagram, the chain is in the message-processing path for both outgoing and incoming messages. When there is a handler chain associated with a service endpoint, SOAP messages are processed as follows:
The client application invokes a method of the service endpoint interface using a generated stub, a dynamic proxy, or the dynamic invocation interface.
The JAX-RPC runtime uses the method argument values to create a SOAP request message.
The request message is passed to the handler chain.
The handler chain passes the request message to each handler in turn. The handler may modify the message, perhaps by adding a header, or may indicate that processing of the message is to be terminated.
If a handler wishes to stop the processing of a message as a result of detecting an error, it must replace that message with another containing a suitable Fault element. A handler may also stop message processing because it can generate the appropriate reply without needing to transmit the message to the server, perhaps because the handler acts as a cache mapping previous requests to received responses. If such a caching handler recognized a request that it had already sent, it might be able to supply a copy of the response that it received to the original request. Whether the handler generates a fault message or supplies a different message, that message is treated in the same way as if it were a reply received from the server, as described starting at Step 8. This situation is illustrated in Figure 6-6.
A handler that detects an error may also throw an exception. In this case, no further processing takes place and the exception is thrown back to application code.
If the message successfully reaches the end of the handler chain, it is sent to the server.
When a reply message or a fault message is received (or the processing of an outgoing message is terminated by a handler and a substitute message provided), it is delivered to the same handler chain. The handler chain first verifies that the handler chain can successfully process all of the headers intended for the receiving node that have their mustUnderstand attribute set to "1". If it cannot, then the message is not processed and a SOAPFaultException is thrown to application code. More details on the handling of the mustUnderstand attribute are provided later in this section.
Next, every handler is given the opportunity to process and modify the response message. This may result in headers being removed, content being modified, or a completely different message being substituted before the next handler is invoked. If a handler throws an exception, that exception is thrown back to application code and the message is not processed further. If a handler indicates (without throwing an exception) that handler processing of the message should end, then no more handlers in the chain are invoked. It does not, however, prevent Step 10 from being performed.
Assuming that no exception has been thrown during processing of the response, the JAX-RPC runtime extracts the values of any output parameters and the return value from the (possibly modified) message, and arranges for them to be returned to the caller of the service endpoint interface method.
The JAX-RPC specification allows the order in which handlers within a chain are invoked to be implementation-dependent. However, the following is likely to be typical behavior:
An outgoing message is passed to handlers in a specific order. In terms of Figure 6-5, this order might be A, B, C.
When a response message or a fault message is received from the server, the handlers are invoked in the reverse order. That is, in Figure 6-5, the processing order for the response would be C, B, A.
If processing for an outgoing message is blocked by a handler, the substituted fault or response message is processed by the same handlers that have already handled the outgoing message, but not by those that have not. For example, if handlers A and B in Figure 6-5 process a message and handler B blocks further processing of that request, then the fault or reply message supplied by handler B and shown in Figure 6-6 is processed by handler B and then handler A, but not by handler C (since it did not see the outgoing message).
In general, handlers in a chain operate independently and therefore do not rely on the presence of other handlers in the chain or the order in which they are invoked.
A handler chain may also be installed on the server side of a web service, as shown in Figure 6-7.
The processing of messages on the server side is similar to that performed for the client:
A SOAP message is received from the underlying transport and passed to the generated tie class for the service endpoint interface.
If a handler chain is configured, the message is given to the handler chain. Otherwise, processing resumes at Step 5.
The handler chain first verifies that its handlers can successfully process all of the headers intended for the receiving node that have their mustUnderstand attribute set to "1". If it cannot, then a fault message is created and processing resumes at Step 8. More details on the handling of the mustUnderstand attribute are provided later in this section.
The handlers in the chain are given the opportunity to process the message in some way. If any handler throws an exception other than a SOAPFaultException, a fault message is created and processing resumes at Step 8. If a handler detects an error that should result in a Fault being returned to the client, it must replace the original message with one containing the appropriate fault details and throw a SOAPFaultException, which causes processing to resume at Step 6. If a handler indicates that processing of the request message should not continue (but does not throw an exception), no further handlers in the chain are invoked and processing continues at step 7. This is the server-side equivalent of the situation shown in Figure 6-6. In this case, the target method of the service endpoint implementation class is not invoked and the handler must install an appropriate reply message before returning control.
If there is no handler chain or the handler chain was completed traversed, the (possibly modified) message is decoded and the appropriate method of the service endpoint implementation class is invoked. If an exception occurs either while the message is being decoded or during the execution of the service endpoint method, then a fault message is generated. Otherwise, the return value and output values from the method call are used to build an appropriate response message.
If a fault message has been created, then the handler chain is given the opportunity to handle it. If any handler throws an exception while processing the fault message, the chain terminates and a new fault message reporting the exception is created instead. If a handler requests that chain processing is complete, then no further handlers are invoked.
Similarly, if a response message is generated, it is passed to the handler chain. Again, if any handler throws an exception while processing the reply, the chain terminates and a fault message reporting the exception is created instead. If a handler requests that chain processing is complete, then no further handlers are invoked.
Finally, the (possibly modified) response or fault message is transmitted to the client.
The classes and interfaces used in connection with message handling belong to the javax.xml.rpc.handler and javax.xml.rpc.handler.soap packages. To implement a handler, you need to create a class that implements the Handler interface shown in Example 6-42.
public interface Handler { public void init(HandlerInfo config); public void destroy( ); public QName[] getHeaders( ); public boolean handleRequest(MessageContext ctx); public boolean handleResponse(MessageContext ctx); public boolean handleFault(MessageContext ctx); }
For convenience, the javax.xml.rpc.handler package contains an abstract class called GenericHandler that provides default implementations of the methods of the Handler interface, with the exception of getHeaders( ). You can use GenericHandler as the base class for your own handlers to avoid having to include empty methods where no specific behavior is required.
Handlers are stateless objects that may be pooled by a JAX-RPC implementation and must provide a public, no-argument constructor. When a handler is created, its init( ) method is called and passed an object of type HandlerInfo that contains configuration information that it may use to initialize itself or customize its behavior. If the JAX-RPC runtime decides that it no longer needs a specific instance of a handler, it calls its destroy( ) method to allow it to release any resources it may have allocated at construction time or during the execution of the init( ) method.
The HandlerInfo object passed to a handler's init( ) method contains information supplied when the handler is configured using the API described in Section 6.8.2.3, later in this chapter. The most useful HandlerInfo method is getHandlerConfig( ):
public java.util.Map getHandlerConfig( );
The Map returned by this method contains properties that can be set at configuration time and used by the handler to customize its behavior. The example handlers shown later in this chapter use this facility to determine whether to print debugging information.
The getHeaders( ) method is required to return the names of the header elements that the handler processes, if any. Each value in the returned array must be a QName object representing the fully qualified name of the top-level element of any such header. Handlers that process only the body of the message or provide other functionality that is not related to a specific SOAP header should return an empty array from this method.
The handleRequest( ), handleResponse( ), and handleFault( ) methods are where the real work of the handler is done. These methods are invoked when the handler chain is processing a SOAP request message, a response message, or a fault message, respectively. As a request message is being processed, the handler chain invokes the handleRequest( ) method of each handler in turn. If this method returns true, then the chain continues processing with the next handler. If it returns false, chain processing terminates and response or fault processing may begin, as described earlier in Section 6.8.2.1. These methods may throw runtime exceptions as well. These also result in chain processing being terminated, with consequences that differ depending on whether the handler chain is associated with the client or server side of the web service, as described in the previous section.
When a message is being processed on either the client or server side, it is associated with an instance of an object that implements the javax.xml.rpc.handler.MessageContext interface. On the client side, a MessageContext object is associated with the handler chain when the request message is created, and the same instance of that object is passed to each handler in the handler chain. When the response message or a fault message is received, the same MessageContext object is then passed to each handler as it processes the inbound message. On the server side, when a request is received, all handlers that process that request are passed the same MessageContext instance. This MessageContext object is available to the servant class that implements the web service, provided that it implements the ServiceLifecycle interface, from the getMessageContext( ) method of ServletEndpointContext and to a session bean implementation via the getMessageContext( ) method of its SessionContext. The MessageContext is also passed to the handlers in the handler chain when the outgoing response or fault message is being processed.
The methods of the MessageContext interface are shown in Example 6-43.
public interface MessageContext { public boolean containsProperty(String name); public Iterator getPropertyNames( ); public Object getProperty(String name); public void setProperty(String name, Object value); public void removeProperty(String name); }
As you can see, MessageContext is essentially a container for property values.[13] Since the same instance of MessageContext is shared between all of the handlers in a chain (and also, on the server side, with the servant class), it can be used to share state between handlers or between one or more handlers and the web service implementation itself. You'll see how this technique can be used in Section 6.8.2.5, later in this chapter.
[13] In the reference implementation, the implementation of this interface simply delegates its behavior to a HashMap.
For SOAP messaging (which is the only form of messaging currently supported by JAX-RPC), the MessageContext object passed to the handlers and the web service itself are actually instances of a SOAP-specific derived interface called javax.xml.rpc.handler.soap.SOAPMessageContext. This method contains a small number of additional methods that are shown in Example 6-44.
public interface SOAPMessageContext extends MessageContext { public SOAPMessage getMessage( ); public void setMessage(SOAPMessage message); public String[] getRoles( ); }
When a SOAP message is received, a reference to it is stored in the SOAPMessageContext by calling the setMessage( ) method. Handlers can then get access to the message by casting their MessageContext argument to SOAPMessageContext and calling the getMessage( ) method. In some cases, a handler may wish to replace the received message with another one, and may call the setMessage( ) method to achieve this. The replacement message is then the one that is processed by the other handlers in then chain and, on the server side, by the web service implementation if the substitution occurs during request processing.
A complete handler chain is managed by an implementation-supplied object that implements the javax.xml.rpc.handler.HandlerChain interface. A HandlerChain is responsible for invoking the handleRequest( ), handleResponse( ), and handleFault( ) methods of each of the handlers that it contains when its own methods of the same name are called, and is responsible for terminating execution of the chain when a handler throws an exception or returns the value false. Since handler chains are managed internally by the JAX-RPC runtime, you don't need to deal with them directly, despite the fact that the HandlerChain interface itself is public. For a description of the HandlerChain interface, refer to the reference section of this book.
Handler chains are associated with service endpoints. For each service endpoint, there may be zero or one handler chain configured on the client side and zero or one handler chain on the server side. Configure a handler chain by adding a handlerChains element to the config.xml file used by wscompile on the client side. The format of the handlerChains element is shown in Example 6-45.
<handlerChains> <chain runAt="client|server" roles="URI list"> <!-- 0 or 1 with "client" and 0 or 1 with "server" --> <handler className="handlerClassName" headers="QName list"> <!--- Any number permitted --> <property name="name" value="value"/> <!--- Any number permitted --> </handler> </chain> </handlerChains>
A handlerChains element can contain zero, one, or two chain elements. The runAt attribute determines whether a client-side or server-side chain is being described. There can be only one of each type within a handlerChains element. The wscompile utility handles chain elements with runAt set to client while generating client-side stubs. The meaning of the optional roles attribute will be described later in this chapter in Section 6.8.2.4.
A chain element may contain any number of handler elements, each of which configures a single message handler. The set of all handler elements describes the content of the handler chain. The className attribute gives the name of the Java class that implements the handler. As mentioned earlier, this class must have a public, no-argument constructor, and must implement the Handler interface. The headers attribute is optional and is described later in Section 6.8.2.4.
Each handler element may have an arbitrary number of nested property elements that provide configuration information for the handler. The value of the className and headers attributes of the handler element along with a Map constructed from the property elements are used to initialize the HandlerInfo object that is passed to the message handler's init( ) method. The property values can be obtained by calling the getHandlerConfig( ) method of the HandlerInfo object.
If you are using the JWSDP, on the server side the information used to build the handler chain for an endpoint is obtained from the handlerChains element in the jaxrpc-ri.xml file and built into the tie classes generated by wsdeploy. For J2EE 1.4, the handler chain is described in the webservices.xml file, as discussed later in this section. The handler chain for each endpoint is constructed when the tie for that endpoint is initialized. There is currently no way for a web service implementation to programmatically access or change the configuration of the handler chain that it is associated with.
On the client side, wscompile gets handler information from the handlerChains element in the config.xml file and adds the code necessary to build a handler chain to the generated stub class for each endpoint. This code accesses a registry of handlers that is owned by the generated Service class for that endpoint and initialized in the constructor of that Service class. The registry is an instance of an implementation-dependent class that implements the javax.xml.rpc.handler.HandlerRegistry interface, which is shown in Example 6-46.
public interface HandlerRegistry { public java.util.List getHandlerChain(QName portName); public void setHandlerChain(QName portName, java.util.List chain); }
As long as you use only stubs generated by wscompile to access web services, you won't need to concern yourself with the HandlerRegistry. However, if you use a Service object obtained from a ServiceFactory (as you would if you invoke a web service using a dynamic proxy or the dynamic invocation interface), that Service object will not have an initialized HandlerRegistry, since it is not created as a result of code generated by wscompile. To ensure that the proper handler chain is constructed in this case, you need to initialize the HandlerRegistry yourself, before calling the Service getPort( ) or createCall( ) methods. An example that demonstrates how this can be done is shown in Section 6.8.2.6, later in this chapter.
Each Handler implements the getHeaders( ) method to return a list of QNames for the headers that it processes if they appear in the messages that are passed to it. When a handler chain is created, the HandlerChain object calls the getHeaders( ) method of each Handler to create a list of the headers that can be handled by the entire chain. This header list is used in conjunction with the role list set, using the roles attribute of the chain element to determine whether the chain can process all of the headers that it needs to be able to handle.
|
The roles attribute is initialized using a list of URIs that represent the SOAP actors that the handlers in the chain represent (refer to Section 3.7.1 for a discussion of SOAP actors and the rules for handling SOAP headers that contain an actor attribute). If this list is non-empty, then the HandlerChain takes the following steps before passing a received request message (on the client side) or a response message (on the server side) to the first handler in the chain for processing:
Gets a list of the headers from the SOAP message that are intended for one of the actors in the actor list, or that are addressed to the actor next.
For each header in this list that has the mustUnderstand attribute set to "1", checks whether the header QName is in the set of QNames for the headers that can be handled by the chain.
If the header is not in the list, then there is no handler that can process the header. The SOAP specification requires that a fault be generated in this case, so the HandlerChain does not proceed with processing of the message but creates a fault message instead.
If the chain element does not have a roles attribute, then it is assumed that only headers that are intended for the ultimate recipient of the message are to be processed and this check does not take place.
To demonstrate how you can make use of a SOAP message handler, we'll reimplement the example used earlier in Section 6.8.1 without mapping the authentication information that appears in the header of the request message, or mapping the timestamp that is placed in the reply message header, to explicit method arguments. Instead, we'll handle these headers entirely in two message handlers � one created for the client side, the other for the server side.
As before, the starting point for this example is the WSDL definition of the service. Since the wire format of the messages will not change from that used in the original implementation, we'll start with a WSDL definition that is exactly the same as that used in the previous version of this example (apart from the fact that the filename is changed, as are the URIs within the file) to reflect the directory structuring of the example source code. Using this WSDL definition, the next step is to create the Java interface definition for the service using wscompile. On this occasion, however, we don't use the -f:explicitcontext option, so that the soap:header elements in the binding section of the WSDL document are ignored. You can generate the interface definitions by opening a command window, making chapter6\handlerbookservice your working directory, and then typing the command:
ant generate-interface
The output from this command is written to the directory chapter6\handlerbookservice\generated\interface\ora\jwsnut\chapter6\handlerbookservice. If you look at the file HandlerBookQuery.java in this directory, you'll see the Java interface definition for this service, which is reproduced here as Example 6-47.
public interface HandlerBookQuery extends java.rmi.Remote { public void log(java.lang.String string_1) throws java.rmi.RemoteException; public int getBookCount( ) throws java.rmi.RemoteException; public java.lang.String getBookTitle(int int_1) throws java.rmi.RemoteException; public void getBookAuthor(java.lang.String string_1, StringHolder string_2) throws HandlerBookServiceException, java.rmi.RemoteException; }
Comparing this with Example 6-39, you'll see that the method call arguments corresponding to the authentication information and the timestamp are no longer present. In this example, these attributes are handled as follows:
On the client side, instead of the client application supplying the authentication information with every method call on the service endpoint interface, a message handler inserts the username and password in the SOAP header of every request message. This is beneficial not only because it simplifies the application code, but also because by substituting a different handler (which is a matter of changing the config.xml file and the jaxrpc-ri.xml or webservices.xml file), an alternative authentication mechanism can be used without requiring a change to application code.
On the server side, the authentication information is extracted from the SOAP message header by a server-side handler that knows the format of the header element that carries it. It is then made available to the web service implementation via the MessageContext object.
Instead of each service endpoint method recording the time at which it was called, as shown in the implementation of the getBookCount( ) method in Example 6-41, the server-side handler inserts a header element containing this information in each response message that it processes.
Let's look first at the implementation of the handler used on the server side, the source code for which you'll find in the file chapter6\handlerbookservice\server\ora\jwsnut\chapter6\handlerbookservice\ServiceHandler.java. The initialization code for this handler is shown in Example 6-48.
public class ServiceHandler extends GenericHandler { // Namespace for types used by this handler private static final String NS_URI = "urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery"; // Namespace prefix used by this handler private static final String NS_PREFIX = "tns"; // Name of the authentication header private static final QName authHeader = new QName(NS_URI, "auth"); // Name of the date/time header private static final QName timeHeader = new QName(NS_URI, "time"); // Formatter for dates private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // SOAPFactory used to create Names private static SOAPFactory factory; // Name of the authentication header as a Name private static Name authHeaderName; // Name of the date/time header as a Name private static Name timeHeaderName; // The headers that this handler is associated with private static QName[] headers; // Debug flag private boolean debug; // Performs initialization public void init(HandlerInfo info) { // Extract the debug setting from the configuration Map config = info.getHandlerConfig( ); String value = (String)config.get("debug"); debug = value == null ? false : Boolean.valueOf(value).booleanValue( ); // Create Names try { factory = SOAPFactory.newInstance( ); authHeaderName = factory.createName("auth", NS_PREFIX, NS_URI); timeHeaderName = factory.createName("time", NS_PREFIX, NS_URI); headers = new QName[] { authHeader, timeHeader }; } catch (SOAPException ex) { throw new JAXRPCException("Init failure", ex); } if (debug) { System.out.println("Server-side handler initialized"); } } // Other code not shown here }
Notice first that the handler class is derived from GenericHandler, which contained default implementations for methods of the Handler interface that do not need to provide special behavior for this example.
For the JWSDP, the init( ) method receives a HandlerInfo object whose content is created from the handler configuration information provided in the jaxrpc-ri.xml file, which is shown in Example 6-49.
<?xml version="1.0" encoding="UTF-8"?> <webServices xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd" version="1.0" targetNamespaceBase="urn:jwsnut.chapter6.handlerbookservice/wsdl/" typeNamespaceBase="urn:jwsnut.chapter6.handlerbookservice/types/"> <endpoint name="HandlerBookQuery" displayName="HandlerBookQuery Port" description="Handler Book Query Port" model="/WEB-INF/model" interface="ora.jwsnut.chapter6.handlerbookservice.HandlerBookQuery" implementation= "ora.jwsnut.chapter6.handlerbookservice.HandlerBookServiceServant"> <handlerChains> <chain runAt="server" xmlns:tns="urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery> <handler className="ora.jwsnut.chapter6.handlerbookservice.ServiceHandler" headers="tns:auth tns:time"> <property name="debug" value="true"/> </handler> </chain> </handlerChains> </endpoint> <endpointMapping endpointName="HandlerBookQuery" urlPattern="/HandlerBookQuery"/> </webServices>
This configuration specifies that the handler chain associated with this service consists of a single handler implemented in the class ora.jwsnut.chapter6.handlerbookservice.ServiceHandler. Since the chain element does not have a roles attribute, the handler is assumed to handle only headers that are intended for the ultimate recipient of each SOAP message. The headers attribute of the handler element indicates that the handler processes two different headers called auth and time, which are names defined in the XML namespace associated with this example (urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery). The property element associated with this handler defines a single property called debug that, in this case, has the value true.
The corresponding server-side configuration for the J2EE 1.4 platform appears in the service's webservices.xml file, which is shown in Example 6-50.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE webservices PUBLIC "-//IBM Corporation, Inc.//DTD J2EE Web services 1.0//EN" "http://www.ibm.com/standards/xml/webservices/j2ee/j2ee_web_services_1_0.dtd"> <webservices> <webservice-description> <webservice-description-name>Book Service Web Application with message handler using JAX-RPC</webservice-description-name> <wsdl-file>HandlerBookQuery.wsdl</wsdl-file> <jaxrpc-mapping-file>WEB-INF/model</jaxrpc-mapping-file> <port-component> <port-component-name>HandlerBookQueryPort</port-component-name> <wsdl-port> <namespaceURI>urn:jwsnut.chapter6.handlerbookservice/wsdl/ HandlerBookQuery</namespaceURI> <localpart>HandlerBookQueryPort</localpart> </wsdl-port> <service-endpoint-interface>ora.jwsnut.chapter6.handlerbookservice. HandlerBookQuery</service-endpoint-interface> <service-impl-bean> <servlet-link>HandlerBookQueryServlet</servlet-link> </service-impl-bean> <!-- Handler for this port --> <handler> <handler-name>Service Handler</handler-name> <handler-class>ora.jwsnut.chapter6.handlerbookservice.ServiceHandler </handler-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <soap-header> <namespaceURI> urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery <localpart>auth</localpart> </namespaceURI> <localpart>auth</localpart> </soap-header> <soap-header> <namespaceURI> urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery </namespaceURI> <localpart>time</localpart> </soap-header> </handler> </port-component> </webservice-description> </webservices>
Here, the headers that the handler will process are described using nested soap-header elements, while the SOAP roles are specified using one or more soap-rule elements. In this case, since there are no soap-role elements, the handlers process only headers intended for the ultimate recipient of each message.
The handler's class name and the header names are made available in the HandlerInfo object passed to its init( ) method, together with the property value, which appears as an entry in the Map that can be retrieved using its getHandlerConfig( ) method. The init( ) method, which is shown in Example 6-48, extracts this value and stores it in an instance variable for later use. This setting determines whether debugging information will be printed. The rest of this method uses the javax.xml.soap.SOAPFactory class to create the names of the header elements that this handler will process. These names are used when calling methods from the SAAJ API when the handler is processing SOAP messages. The handler's processing for SOAP request messages is shown in Example 6-51.
public boolean handleRequest(MessageContext ctx) { try { if (debug) { System.out.println("handleRequest called"); } String userName = null; String password = null; SOAPMessage message = ((SOAPMessageContext)ctx).getMessage( ); SOAPHeader header = message.getSOAPPart().getEnvelope( ).getHeader( ); if (header != null) { // Locate the "auth" header Iterator iter = header.getChildElements(authHeaderName); if (iter.hasNext( )) { SOAPElement element = (SOAPElement)iter.next( ); Iterator children = element.getChildElements( ); while (children.hasNext( )) { SOAPElement childElement = (SOAPElement)children.next( ); String localPart = childElement.getElementName( ) .getLocalName( ); if (localPart.equals("UserName") && userName == null) { userName = childElement.getValue( ); } else if (localPart.equals("Password") && password == null) { password = childElement.getValue( ); } } // Remove this header element.detachNode( ); } // Remove any other "auth" headers. while (iter.hasNext( )) { ((SOAPElement)iter.next( )).detachNode( ); } // Install the user name and password in the MessageContext. // This installs null if either attribute was missing. ctx.setProperty(HandlerBookServiceConstants.USERNAME_PROPERTY, userName); ctx.setProperty(HandlerBookServiceConstants.PASSWORD_PROPERTY, password); if (debug) { System.out.println("Got auth: user: [" + userName + "], password: [" + password + "]"); } } } catch (SOAPException ex) { throw new JAXRPCException("Error in handleRequest", ex); } return true; }
This code expects to find a header called auth carrying the username and password for the user calling the methods of the web service endpoint on which it is configured. Most of this code is concerned with using the SAAJ APIs described in Chapter 3 to locate this header, the format of which is shown in Example 6-37. If the header is present, the code extracts the values that it contains. The points of relevance to the construction of the message handler itself are the following:
In order to process the SOAP header, this method needs to be able to access the SOAP message. This is achieved by casting the MessageContext object passed to it to the type SOAPMessageContext and then calling its getMessage( ) method. Almost every handleRequest( ), handleResponse( ), and handleFault( ) method in a message handler will contain code like this.
Once the auth header is found and its content extracted, it is removed from the SOAP message. This follows the rules for header handling in the SOAP specification (and also described in Section 3.7).
Having obtained the username and password, the handler needs to be able to make them available to the methods in the servant class that implement the service endpoint interface. It does this by storing them under well-known names in the MessageContext, which is also available to the service methods. Handlers can also use the MessageContext to pass information to other handlers in the handler chain.
The difference that this handler makes to the service class can be seen by looking at the implementation of the same methods that were shown in Example 6-41, where explicit context was used to convey the information now extracted by the message handler. The new implementations of these methods are shown in Example 6-52.
public int getBookCount( ) { String[] titles = HandlerBookServiceServantData.getBookTitles( ); return titles == null || !checkAccess( ) ? 0 : titles.length; } private boolean checkAccess( ) { boolean allowed = false; // Get the username and password from the MessageContext MessageContext context = endpointContext.getMessageContext( ); String callingUser = (String)context.getProperty( HandlerBookServiceConstants.USERNAME_PROPERTY); String callingPwd = (String)context.getProperty( HandlerBookServiceConstants.PASSWORD_PROPERTY); if (userName != null && password != null) { // Authentication is configured. return userName.equals(callingUser) && password.equals(callingPwd); } return allowed; }
As you can see, the getBookCount( ) method no longer receives any authentication information. Instead, it simply invokes the helper method checkAccess( ), and relies upon it to obtain the caller's username and password. The checkAccess( ) method gets the user's credentials from the MessageContext (where the message handler stored them), a reference to which it obtains from the ServletEndpointContext object that was passed to the servant's init( ) method.
You'll notice also that the getBookCount( ) method no longer provides the time at which it was invoked. In the new implementation, this information is no longer supplied by the servant class. Instead, it is added to the header of each response message by the handleResponse( ) method of the message handler, which is shown in Example 6-53.
public boolean handleResponse(MessageContext ctx) { try { if (debug) { System.out.println("handleResponse called"); } SOAPMessage message = ((SOAPMessageContext)ctx).getMessage( ); SOAPHeader header = message.getSOAPPart().getEnvelope( ).getHeader( ); if (header == null) { header = message.getSOAPPart().getEnvelope( ).addHeader( ); } SOAPElement element = header.addChildElement(timeHeaderName); String text = format.format(new Date( )); element.addTextNode(text); if (debug) { System.out.println("Added time header, value " + text); } } catch (SOAPException ex) { throw new JAXRPCException("Error in handleRequest", ex); } return true; }
The implementation of the handler used on the client side, which you'll find in the file chapter6\handlerbookservice\client\ora\jwsnut\chapter6\client\ClientHandler.java, is very similar to that of the server-side handler just shown. Since almost all of the code is concerned with simple header manipulation using the SAAJ APIs covered in Chapter 3, I'm not going to show it here. The handler's init( ) method obtains the username and password to be used from the system properties and stores them for use by the handleRequest( ) method, which places them in an auth element that it adds to the SOAP header of each message that is passed to it. The handleResponse( ) method looks for a time element in the SOAP header of the response messages that it receives and extracts from it the timestamp inserted by the handler on the server side. There is no way, however, for the handler to communicate this value to the client application in a manner that does not require the application to be tied to the handler itself, since there is no client-side API that exposes the MessageContext object in the same way that there is on the server side. This being the case, having extracted the timestamp, the handler simply prints it on the System.out stream.
To activate a client-side handler, include a handlerChains element in the config.xml file from which the client-side stubs are generated, as shown in Example 6-54. Note that, in this case, the runAt attribute of the chain element has the value client. It is possible to include a server-side handler chain in the config.xml file, but this would only be useful if you intend to use wscompile to generate server-side artifacts.
<?xml version="1.0" encoding="UTF-8" ?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <wsdl location= "C:\JWSNutshell\draft1\examples\chapter6\handlerbookservice/ HandlerBookQuery.wsdl" packageName="ora.jwsnut.chapter6.handlerbookservice"> <handlerChains> <chain runAt="client" xmlns:tns="urn:jwsnut.chapter6.handlerbookservice/wsdl/ HandlerBookQuery"> <handler className="ora.jwsnut.chapter6.client.ClientHandler" headers="tns:auth tns:time"> <property name="debug" value="true"/> </handler> </chain> </handlerChains> </wsdl> </configuration>
You can run this example by deploying the service implementation using the command:
ant deploy
To run the client application, use the commands:
ant compile-client ant run-client
Since debugging has been enabled by setting the debug property in both the client- and server-side handlers, you'll see messages printed as the handlers' handleRequest( ) and handleResponse( ) methods are called. An extract from the client-side output, reformatted for the sake of readability, is shown here:
[java] handleRequest called [java] OUTGOING MESSAGE: [java] <?xml version="1.0" encoding="UTF-8"?> [java] <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="urn:jwsnut.chapter6.handlerbookservice/types/HandlerBookQuery" xmlns:ns1="urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <soap-env:Header> <tns:auth xmlns:tns= "urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery"> <UserName>JWSUserName</UserName> <Password>JWSPassword</Password> </tns:auth> </soap-env:Header> <soap-env:Body> <ns1:log> <String_1 xsi:type="xsd:string"> HandlerBookServiceClient: success </String_1> </ns1:log> </soap-env:Body> </soap-env:Envelope> [java] handleResponse called [java] Request processed at: 2002-10-24T21:13:59
The client handler's handleRequest( ) method prints the complete SOAP request message, including the auth header that it adds (highlighted in bold in the previous code listing), while the handleResponse( ) method displays the timestamp obtained from the time header provided by the server-side handler. Similar output from the server-side handler can be found in the catalina.out file in the logs directory of the Tomcat web server, or in the file used to capture the System.out stream for the J2EE application server.
When your client application uses stubs generated by wscompile, the generated Service object contains the code necessary to create a handler chain if one is specified in the config.xml file. However, a Service object obtained from a ServiceFactory is generic and therefore cannot create an application-specific handler chain. To use handler chains in conjunction with dynamic proxies or the dynamic invocation interface, you have to add code to your application to install the necessary information in the HandlerRegistry associated with the Service object before using its getPort( ) or createCall( ) methods. Example 6-55 shows an extract from a client application that uses a dynamic proxy to access the book web service that we have been using in this section.
// Form the names of the service and of the port QName serviceName = new QName(NS_URI, "HandlerBookService"); QName portName = new QName(NS_URI, "HandlerBookQueryPort"); // Get the Service ServiceFactory factory = ServiceFactory.newInstance( ); Service service = factory.createService(wsdlURL, serviceName); // Build a handler chain with one handler QName[] headers = new QName[] { new QName(NS_URI, "auth"), new QName(NS_URI, "time") }; HashMap map = new HashMap( ); map.put("debug", "true"); HandlerInfo info = new HandlerInfo(ClientHandler.class, map, headers); ArrayList handlerList = new ArrayList( ); handlerList.add(info); // Add the handler chain to the HandlerRegistry HandlerRegistry handlerRegistry = service.getHandlerRegistry( ); handlerRegistry.setHandlerChain(portName, handlerList); // Now get the dynamic proxy HandlerBookQuery bookQuery = (HandlerBookQuery)service.getPort(portName, HandlerBookQuery.class);
This code gets a reference to the HandlerRegistry by calling the getHandlerRegistry( ) method of the Service object obtained from the ServiceFactory. A handler chain is configured for a specific service endpoint using the HandlerRegistry setHandlerChain( ) method:
public void setHandlerChain(QName portName, java.util.List handlerChain);
The portName argument provides the name of the port for which the handler is to be configured in the form of a QName. The port name can, of course, be obtained from the port element in the WSDL definition of the service:
final String NS_URI = "urn:jwsnut.chapter6.handlerbookservice/wsdl/HandlerBookQuery"; QName portName = new QName(NS_URI, "HandlerBookQueryPort");
The handlerChain argument is a java.util.List object containing an entry of type
HandlerInfo
containing configuration information for each handler in the chain. In the reference implementation, the order of elements in this list determines the order in which the handlers are invoked during request processing. This behavior however, is implementation-dependent. In this example, the handler chain has only one handler.
The HandlerInfo constructor requires three arguments:
public HandlerInfo(Class handlerClass, java.util.Map config, QName[] headers);
The first argument is the Class object for the handler implementation class. The second argument is a Map containing configuration parameters for the handler. This map should contain the values that you ordinarily supply using property elements in the config.xml file. In this case the Map is initialized so that the debug property has the value true, which results in the same configuration as that shown in Example 6-54. The final argument lists the header types that the handler chain can process.
Once the list of handlers is constructed, it is installed in the registry:
// Add the handler chain the HandlerRegistry HandlerRegistry handlerRegistry = service.getHandlerRegistry( ); handlerRegistry.setHandlerChain(portName, handlerList);
With registry initialization complete, it is now possible to use the getPort( ) method to obtain a dynamic proxy for the target port (or one of the createCall( ) methods if you intend to use the dynamic invocation interface). The proxy uses the information in the registry to construct the handler chain before the first message is sent to the server. You can verify that this works by running the code using the command:
ant run-proxy-client
This should produce the same result as the client used in the previous section, which used precompiled stubs to access the web service.