A SOAP message may also contain one or more attachments using the MIME encoding, as Listing 10.11 shows. This is often refered to as a compound message. The attachments are referenced in the SOAP message with an HREF, analogous to how HTML anchor tags are used to create links on the same Web page. The special characters in Listing 10.11 are the binary content of the attachment printed as text.
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope... > <env:Body> <storeDocumentService> <!--Some XML Here--> <something xsi:type="ns1:something" href="cid:ID1"/> </storeDocumentService> </env:Body> </env:Envelope> --3317565.1028340932732.JavaMail.Administrator.BYTECODE Content-Type: application/octet-stream Content-Id: ID1 ÐÏ_ài±_á
Sending information in an attachment rather than in the SOAP message body is more efficient, because smaller SOAP message bodies are processed faster. The message contains only a reference to the data and not the data itself, which reduces the translation time in mapping the data to Java objects. JAX-RPC uses the JavaBeans activation framework for dealing with SOAP attachments. When unmarshalling this message to Java, the JAX-RPC runtime can use either of two mapping techniques:
It can map well-known MIME types to Java objects, as per Table 10.7, and vice versa, using built-in DataHandlers and DataContentHandlers in the runtime.
MIME |
Type |
---|---|
image/gif |
java.awt.Image |
image/jpeg |
java.awt.Image |
text/plain |
java.lang.String |
multipart/* |
javax.mail.internet.MimeMultipart |
text/xml or application/xml |
javax.xml.transform.Source |
It can map the attachment to a javax.activation.DataHandler using the JavaBeans Activation framework, and vice versa.
What this essentially means is that if a method in a service implementation is exposed in a Web service and has a return type that contains either a Java type, as per the mappings shown in Table 10.2, or a DataHandler, the runtime will marshal that as an attachment to the outgoing SOAP message. If the argument is of the type in Table 10.7 or is a DataHandler, it will be passed the corresponding attachment from the incoming SOAP message. The content of the attachment can then be extracted using a getContent() on the DataHandler. If the installed DataContentHandler does not understand the content, it will return a java.io.InputStream object with the raw bytes.
Let us now look at an example of a Flute Bank Web service that stores and archives any incoming documents it receives from partners. The remote interface defines a single method, as shown in the following code.
public interface AttachmentService extends Remote{ public String storeDocumentService(DataHandler dh,String filename) throws RemoteException; }
The service implementation (Listing 10.12a) is also straightforward; it just extracts the content from the DataHandler and stores it to a file. It returns a date/timestamp to the caller.
public class AttachmentServiceImpl implements AttachmentService { /** * This method implements a web service that stores any attachment it receives. */ public String storeDocumentService(DataHandler dh, String filename) { try{ BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream (filename)); BufferedInputStream in = new BufferedInputStream (dh.getInputStream()); byte[] buffer = new byte[256]; while (true) { int bytesRead = in.read(buffer); if (bytesRead == -1) break; out.write(buffer, 0, bytesRead); } in.close(); out.close(); }catch(Exception e){ System.out.println(e); return e.toString(); } return ("File processes succesfully "+ filename+""+new Date()); } }
Listing 10.12b shows the xrpcc configuration used to generate stubs and ties.
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="attachservice" targetNamespace="http://www.flutebank.com/xml" typeNamespace="http://www.flutebank.com/xml" packageName="com.flutebank.attachmentservice"> <interface name="com.flutebank.attachmentservice.AttachmentService" servantName="com.flutebank.attachmentservice.AttachmentServiceImpl"/> </service> </configuration>
The relevant extract from the client code is shown below, where the stub is instantiated and the service invoked:
Attachservice_Impl() service =new Attachservice_Impl(); AttachmentService_Stub stub=(AttachmentService_Stub) (service.getAttachmentServicePort()); stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,url); DataHandler dh = new DataHandler(new FileDataSource(filename)); String response = stub.storeDocumentService(dh,filename); System.out.println("Response from server "+ response);
The SOAP request to the server includes an attachment, as shown below. The MIME segments are highlighted:
POST /attachmentservice/jaxrpc/AttachmentService HTTP/1.1 Content-Type: multipart/related; type="text/xml"; boundary= 3317565.1028340932732.JavaMail.Administrator.BYTECODE Content-Length: 26994 SOAPAction: "" User-Agent: Java1.3.1_01 Host: 127.0.0.1:9090 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive --3317565.1028340932732.JavaMail.Administrator.BYTECODE Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <env: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= "http://www.flutebank.com/xml" xmlns:ns1="http://java.sun.com/jax-rpc-ri/internal" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><env:Body> <ns0:storeDocumentService><DataHandler_1 xsi:type="ns1:datahandler" href="cid:ID1"/> <String_2 xsi:type="xsd:string">Uploadme.doc</String_2></ns0:storeDocumentService> </env:Body></env:Envelope> --3317565.1028340932732.JavaMail.Administrator.BYTECODE Content-Type: application/octet-stream Content-Id: ID1 ÐÏ_ࡱ_á
xrpcc contains an option—Xdatahandleronly—that forces attachments to always map to the DataHandler, instead of to the mappings shown in Table 10.7.
A SOAP message handler is a Java class that provides a filtering mechanism for preprocessing and postprocessing the SOAP message, by intercepting it and acting on the SOAP request and response. As Figure 10.7 shows, a handler can be used on the client side, server side, or both. Handlers can be used to add features to a service call and are a good means to layer additional functionality over the core message. They are useful because they provide the ability to introduce security services, business processing, and error handling. They also permit managing the selection of content creation strategies in both service consumers and service implementations without changing client or server code.
All handler implementations must implement the javax.xml.rpc.handler.Handler interface shown below:
public interface Handler{ public abstract void init(HandlerInfo handlerinfo); public abstract boolean handleRequest(MessageContext messagecontext); public abstract boolean handleResponse(MessageContext messagecontext); public abstract boolean handleFault(MessageContext messagecontext); public abstract void destroy(); public abstract QName[] getHeaders(); }
The handler is passed an instance of a MessageContext, which can be used to access the underlying Soap with Attachments API for Java (SAAJ) javax.xml.soap.SOAPMessage that represents the actual message. It can also be used to pass objects between handlers in the chain, to share state information specific to a request. Note that a handler is always stateless itself and should not hold any message-specific state in an instance variable. The lifecycle of a handler instance is quite similar to that of a servlet:
The runtime initializes the handler by calling the init() method and passing configuration information to the instance via the HandlerInfo object. This is a useful place to obtain references to reusable resources.
Depending on the stage of request processing, the handleRequest(), handleResponse(), or handleFault() method is invoked.
The runtime can call these methods multiple times from different threads that handle different requests and can even pool handler instances for optimization.
When the runtime is done or is under resource constraints, it will invoke the destroy() method, which is a good place to release the resources obtained in the init() method.
Multiple handlers can be combined together an ordered group called a handler chain. Chained handlers are invoked in the order in which they are configured. When a handler completes its processing, it passes the result to the next handler in the chain. Chaining and managing communication between handlers in a chain is done by the runtime. Developers write handlers as individual units that do not need to be aware of other handlers and are thus highly reusable.
The order in which handlers are deployed is important. For example, if a client sends an encrypted request in a compressed format, the handlers on the server must first decompress and then decrypt the input. Like individual handlers, chains can be defined on the client, the server, or both. The steps below describe how execution occurs in a chain (see Figure 10.7):
The handleRequest() methods of the handlers in the chain on the client are all executed, in the order specified. Any of these handleRequest() methods might change the SOAP message request.
When the handleRequest() method of the last handler in the chain has been executed on the client side, the runtime dispatches the request to the server.
When the endpoint receives the request, it invokes the handleRequest() methods of the handlers in the chain on the server, in the order specified in the chain.
When all the handlers are done processing the request, the endpoint delegates the invocation to the service implementation via the tie.
When the service has completed its work, the runtime invokes the handleResponse() methods of the handlers in the chain on the server, in reverse order. The last handler to process the request will be the first to process the response. Any of these handleResponse() methods might change the SOAP message response.
When the client receives the response from the server, the handleResponse() methods of the chain on the client are executed in the same reverse manner. Any handler can change the SOAP message.
The response is then returned to the client application that invoked the Web service.
In a chain, if any of the handle methods in the handler return true, the next handler in the chain is invoked.
Request processing can be terminated by returning false. As Figure 10.8 shows, developers can throw a SOAPFaultException to indicate a SOAP fault or a JAX-RPCException and trigger the handleFault callbacks in the handler. Table 10.8 describes the main classes and interfaces relevant to handlers.
javax.xml.rpc.handler.Handler |
Must be implemented by a handler class. |
javax.xml.rpc.handler.HandlerInfo |
Contains information about the handler—in particular, the initialization parameters. |
javax.xml.rpc.handler.MessageContext |
Abstracts the message processed by the handler and contains getProperty(String) and setProperty (String, Object) methods that can be used to share state between handlers in a handler chain. This is analogous to the pageContext in JSPs or a ServletContext in servlets. |
javax.xml.rpc.handler.soap.SOAP MessageContext |
Extends the MessageContext and provides access to the actual SOAP message. It also contains the getRoles() method, which returns the SOAP actor roles associated with the HandlerChain. |
javax.xml.soap.SOAPMessage |
Object that contains the actual request or response SOAP message, including its header, body, and attachment. |
javax.xml.rpc.handler.HandlerChain |
Implemented by the JAX-RPC implementation to represent a chain. A HandlerChain can have SOAP actor roles associated with it. |
Handlers and handler chains offer a valuable tool to architects. We list below some best practices and usage scenarios for handlers:
Introducing security. A handler can be used to encrypt and decrypt the header or body data, using symmetric or asymmetric ciphering techniques. Clients use a handler to encrypt data before sending the SOAP request. A handler on the server decrypts the data before invoking business components, such as EJBs, and encrypts the outgoing response after business processing occurs.
Processing metadata. A handler can be used to access and manipulate the SOAP header containing metadata or context information about the service invocation or service consumer.
Validating data. Intercepting the request before any processing occurs on the data and validating the request or attachment in the request against a schema, especially for a handling compound messages with XML attachments is best done using handlers.
Handling data content. Handlers can be used to process SOAP attachments—for example, plain text, XML, JPEG images, and octet streams.
Optimizing and improving performance. Handlers can be used to introduce optimizations in service processing by introducing features such as
Implementing intermediaries. Chapter 4 introduced the concepts of actors, intermediaries, and roles. To recall, a SOAP message may pass though intermediaries capable of processing and forwarding the request. The SOAP message may contain header information intended only for an intermediary's consumption. The targeted intermediary will process that particular header and ensure that it is not passed along. The SOAP actor attribute is a URI that indicates whom the header intended for. The actor next corresponds to a URI of http://schemas.xmlsoap.org/soap/actor/next and indicates that the header element is intended for the first SOAP application that processes the message.
Handlers offer a good mechanism to implement SOAP intermediaries and process headers. When the handler chain executes, the runtime will identify the SOAP actor roles for which the chain is configured and ensure that the handlers are passed the header blocks they need. If the processing was unsuccessful or any of the mandatory headers is not present, a corresponding SOAP fault (e.g., a SOAP MustUnderstand fault) is generated and propagated back to the client.
Message handlers can be configured in two ways: programmatically, using JAX-RPC API, or declaratively, using a JAX-RPC runtime-provided tool or deployment descriptor. Client-side handlers can be configured either way, but server-side handlers can be be configured only declaratively. The fragment below shows the relevant extract for xrpcc in the reference implementation given in Listing 10.3. The runAt property can be client or server, indicating where the handler is to be deployed, and the property fields indicate arbitrary properties (e.g., configuration information) required by the handlers. Multiple handlers can be registered per interface or per service.
<handlerChains> <chain runAt="client|serverw" roles=""> <handler className=""headers=""> <property name=""value=""/> </handler> </chain> </handlerChains>
Programmatic registration of handlers on the client can be done in code such as the following:
ServiceFactory factory = ServiceFactory.newInstance(); Service service = factory.createService (...); HandlerRegistry registry = service.getHandlerRegistry(); // pass the namespace and portname to get the handler chain object List chain = registry.getHandlerChain(new QName(...)); Map config =... //configuration poperties Qname headers[]=... //headers HandlerInfo info = new HandlerInfo(MyHandler.class, config,headers); chain.add(info);
Let us now look at an example of using handlers. Flute Bank has exposed a service that allows third-party vendors to send sensitive information about customers as a part of a larger business transaction. The code below shows how a handler can be implemented on both the client and server sides to first compress that information and then encrypt it, using password-based symmetric ciphering (PBEWithMD5AndDES).
As Figure 10.9 shows, the client-side handler intercepts the request, compresses the outgoing data, and encrypts it, using a symmetric cipher. (Listing 10.13 uses JCE, the Java Cryptography Extension API bundled with JDK 1.4.) Once this is done, it places the data back in the SOAP message and sends the request on its way to the service.
public class SecureZipClientHandler implements Handler { private static final byte salt[] = new byte[8]; private static final int iterations =1; private final static String algorithm = "PBEWithMD5AndDES"; private static SecretKeyFactory skf; private static PBEParameterSpec aps; private final static char[] password = "1eallysecurepassword".toCharArray(); public void init(HandlerInfo hi) { try { // Initialize JCE and the key factory Security.addProvider(new com.sun.crypto.provider.SunJCE()); skf = SecretKeyFactory.getInstance(algorithm); aps = new PBEParameterSpec(salt,iterations); } catch (Exception e) { System.out.println(e); } } /** The handlerequest method that intercepts the outgoing request from the client */ public boolean handleRequest(MessageContext context) { try { SOAPMessageContext smc = (SOAPMessageContext)context; SOAPMessage msg = smc.getMessage(); SOAPPart sp = msg.getSOAPPart(); SOAPEnvelope se = sp.getEnvelope(); // next step based on the processing model for this handler SOAPBody body = se.getBody(); Iterator it = body.getChildElements(); SOAPElement opElem = (SOAPElement)it.next(); it = opElem.getChildElements(); SOAPElement pin = (SOAPElement)it.next(); it = pin.getChildElements(); Text textNode = (Text)it.next(); textNode.detachNode(); String encContent = textNode.getValue(); // Use a utility class to decode the Base64 encoded binary SOAP data byte[] contentBytes = Base64.decode(encContent); // zip the content ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream zos = new GZIPOutputStream(baos); zos.write(contentBytes); zos.flush(); zos.finish(); zos.close(); // Encrypt the content byte[] zippedbytes = encrypt(baos.toByteArray()); // Use a utility class to encode the bytes back to the binary SOAP data String zippedContent = Base64.encode(zippedbytes); System.out.println("Client handler done with encryption and compression"); // Add the content to the outgoing message pin.addTextNode(zippedContent); return true; } catch (Exception e) { System.out.println(e); return false; } } private static byte[] encrypt(byte[] clear) throws Exception { byte[] ciphertext = null; PBEKeySpec ks = new PBEKeySpec(password); SecretKey key = skf.generateSecret(ks); Cipher desCipher = Cipher.getInstance(algorithm); desCipher.init(Cipher.ENCRYPT_MODE, key,aps); ciphertext = desCipher.doFinal(clear); return ciphertext; } /* The handleResponse method does nothing on the response returned from the * server. Only outgoing data needs to be encrypted and compressed. */ public boolean handleResponse(MessageContext context) { return true; } // Other Handler methods with empty implementations not shown }
The handler on the server side intercepts the request from the endpoint, decrypts the data using the same password as the client, and decompresses the data. It then places the data back on the SOAP request and sends it on the way to the service implementation or tie, as Listing 10.14 shows.
public class SecureZipServerHandler implements Handler { // member variables are identical to client handler shown previously public void init(HandlerInfo hi) { // Initialize JCE here identically to the client handler shown previously } public boolean handleRequest(MessageContext context) { try { SOAPMessageContext smc = (SOAPMessageContext)context; SOAPMessage msg = smc.getMessage(); SOAPPart sp = msg.getSOAPPart(); SOAPEnvelope se = sp.getEnvelope(); // next step based on the processing model for this handler SOAPBody body = se.getBody(); Iterator it = body.getChildElements(); SOAPElement op = (SOAPElement)it.next(); SOAPElement param = (SOAPElement)op.getChildElements().next(); Text textNode = (Text)param.getChildElements().next(); String zippedenccontent = textNode.getValue(); System.out.println(zippedenccontent); textNode.detachNode(); // Use a utility class to decode the Base64 encoded binary SOAP data byte[] rawbytes = Base64.decode(zippedenccontent); // First decrypt the data using ciphers rawbytes = decrypt(rawbytes); // unzip the data ByteArrayInputStream bais = new ByteArrayInputStream(rawbytes); GZIPInputStream zis = new GZIPInputStream(bais); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int c = -1; while ((c = zis.read())!= -1) { baos.write(c); } baos.flush(); byte[] contentBytes = baos.toByteArray(); System.out.println("Server handler done with decryption and decompression"); // Use a utility class to encode bytes to Base64 binary SOAP data String encContent = Base64.encode(contentBytes); param.addTextNode(encContent); return true; } catch (Exception e) { System.out.println(e); return false; } } // method to decrypt bytes private static byte[] decrypt(byte[] input) throws Exception { byte[] cleartext1 = null; PBEKeySpec ks = new PBEKeySpec(password); SecretKey key = skf.generateSecret(ks); Cipher desCipher = Cipher.getInstance(algorithm); desCipher.init(Cipher.DECRYPT_MODE, key,aps); cleartext1 = desCipher.doFinal(input); return cleartext1; } // The server does not need to process the outgoing response to the client public boolean handleResponse(MessageContext context) { return true; } // Other Handler methods with empty implementations not shown }
The service implementation in Listing 10.15 is no different from any of the previous examples and requires no additional code. Note that in this case, the service implementation is not aware of any of the changes (compression, encryption, decryption, and decompression) applied to the SOAP message between the time the client initiated the request and the time it was processed.
public interface Fileservice extends Remote{ public String acceptContent(byte[] parameter_in) throws RemoteException; } public class FileserviceImpl implements Fileservice { public String acceptContent(byte[] input) throws RemoteException { try { BufferedOutputStream fos= new BufferedOutputStream (new FileOutputStream("Myfile.doc")); fos.write(input,0,input.length); fos.flush(); fos.close(); }catch(Exception e){ System.out.println(e); } return "Data sucessfully processed and timestamped as:"+ new Date(); }
The client code also does not require any modification and remains the same as any of the previous examples:
// instantiate the service. Contentservice_Impl service = new Contentservice_Impl(); Fileservice_Stub stub =(Fileservice_Stub) service.getFileservicePort(); stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,args[0]); // get the content of file to be sent byte[] rawbytes = readFile(args[1]); // send the content String timestamp= stub.acceptContent(rawbytes);
What is different from the previous examples is the configuration file shown in Listing 10.16 for xrpcc, where the handlers are declaratively specified.
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="Contentservice" targetNamespace="http://www.flutebank.com/xml" typeNamespace=http://www.flutebank.com/xml packageName="com.flutebank.encryptedposervice"> <interface name="com.flutebank.encryptedposervice.Fileservice" servantName="com.flutebank.encryptedposervice.FileserviceImpl"/> <handlerChains> <chain runAt="client"> <handler className="com.flutebank.encryptedposervice.SecureZipClientHandler"> </handler> </chain> <chain runAt="server"> <handler className="com.flutebank.encryptedposervice.SecureZipServerHandler"> </handler> </chain> </handlerChains> </service> </configuration>
Though handlers offer a nice way of pre- and postprocessing the SOAP message, certain issues must be kept in mind:
If the handler code introduces propietary modifications in the outgoing SOAP message, the service may no longer be interoperable with other platforms. For example, just based on WSDL, the caller of the above service will never be able to deduce that the server endpoint expects the data in a particular compressed and encrypted format.
Introducing handlers that alter the response message at an endpoint may break existing clients written for the service interface.
Introducing another layer of pre- and postprocessing of the SOAP message may degrade performance by increasing reponse times.
Earlier in this chapter, we mentioned the use of attachements for creating compound messages. Let us look at a possible realization, shown in Figure 10.10.
A business document, such as a purchase order or invoice in XML format, is sent as an attachment to the SOAP message in the request. The service definition exposes a method similar to
which receives the message and in turn maps the attachment as per the mapping in Table 10.7. An XML attachment (text/xml MIME type) automatically maps to the javax.xml.transform.Source. The service could then process that XML representation in many ways. For example, it could parsed and transformed it, place it on a JMS queue, or even pass it to a JAXM provider, as Figure 10.10 shows. Additionally, handlers could intercept the message and perform a hard validation against a schema for the document, if necessary.
The difference between this asynchronous model and the one-way RPC is essentially that the client receives a response from the endpoint, because of its asynchronous persistence framework using JMS. If, as a result of HTTP issues, the client could not receive that response, it is the client's prerogative to retry the invocation or query the service. The latter would require introspection into the integration tier (e.g., JMS queue). The service has no way to communicate back to the HTTP client with a callback unless the client realizes a similar service on its side. This realization, though useful in most scenarios, is pseudo-asynchronous. Look at asynchronous messaging with JAXM and messaging profiles in Chapter 11.
CORBA developers would already be familiar with the concept of in, out, and inout parameters and Holder classes. Like IDL, operations in WSDL may take out or inout parameters as well as in parameters. To understand the in, out, and inout concepts, consider the following signature:
In Java, the somearg is the argument the method receives, and the Something is what the method returns after doing its work. However if clients pass the somearg as an object and expect the method to change that value, it is an inout parameter. For example, consider the following code:
Somearg param= Somearg(...).; Something val= myMethod(param); if (param.xxx){ // }
The above coding practice is discouraged in Java but is used in other languages, such as C and C++. Java passes parameters only by value and has no concept of out or inout parameters; therefore, in JAX-RPC these are mapped Holder classes. In place of the out parameter, a Java method will take an instance of the Holder class of the corresponding type. The result assigned to the out or inout parameter is assigned to the value field of the Holder class.
A service operation signature written in Java will typically return a single value: a primitive or a JavaBean. If there is a need for the service operation to return multiple values, the data type of the return value can be a complex type, such as an object with multiple parts (e.g. a Portfolio object with many Position objects) or an array. The third alternative is to specify that one or more of the parameters of the Web service operation be out or inout parameters.
For example, assume a Web service operation contains one out parameter, and the operation is implemented with a Java method. The method sets the value of the out parameter and sends this value back the client application that invoked it. The client application can then access the value of this out parameter as if it were a return value. The code below illustrates this with a method whose second parameter is an inout parameter:
public float payBalance(String userid,javax.xml.rpc.holders.IntHolder balance) { System.out.println ("The input value is: "+ balance.value); // do some work here balance.value = 90; // the new value of the out parameter }
When the client invokes the above method with two parameters, a String and an integer, it will be returned two values: a float and an integer. If at invocation the balance parameter value was 1000 when the method completed, the value of the second parameter is now 90 and will also be returned to the client.
IntHolder inoutbalance = new IntHolder(1000); System.out.println("Holder value is "+ inoutbalance.value); float interest= service.payBalance("johnmalkovich",inoutbalance); System.out.println("Interest charged on credit card is "+ interest); System.out.println("Remaining balance,holder value is "+ inoutbalance.value);
The above client code invoking the above service implementation will produce the following output:
Holder value is 1000 Interest charged on credit card is 9.0 Remaining balance, holder value is 90
Holder classes for out and inout parameters must implement the javax.xml .rpc.holders.Holder interface. In the service implementation, use the value field to first access the input value of an inout parameter and then set the value of out and inout parameters.
If the out or inout parameter is a standard data type, JAX-RPC provides a set of holder classes in the javax.xml.rpc.holders package, listed in Table 10.9.
Built-in holder class |
Java data type it holds |
javax.xml.rpc.holders.BooleanHolder |
boolean |
javax.xml.rpc.holders.ByteHolder |
Byte |
javax.xml.rpc.holders.ShortHolder |
short |
javax.xml.rpc.holders.IntHolder |
Int |
javax.xml.rpc.holders.LongHolder |
Long |
javax.xml.rpc.holders.FloatHolder |
float |
javax.xml.rpc.holders.DoubleHolder |
double |
javax.xml.rpc.holders.BigDecimalHolder |
Java.math.BigDecimal |
javax.xml.rpc.holders.BigIntegerHolder |
Java.math.BigInteger |
javax.xml.rpc.holders.ByteArrayHolder |
Byte[] |
javax.xml.rpc.holders.CalendarHolder |
Java.util.Calendar |
javax.xml.rpc.holders.QnameHolder |
javax.xml.namespace.QName |
javax.xml.rpc.holders.StringHolder |
Java.lang.String |
If the data type of the parameter is not provided, developers must create their own implementation of the javax.xml.rpc.holders.Holder interface to handle out and inout parameters, based on the following guidelines:
Name the implementation class XXXHolder, where XXX is the name of the complex type. For example, if the complex type is called Portfolio, the implementation class is called PortfolioHolder.
Create a public field called value, whose data type is the same as that of the parameter.
Create a default constructor that initializes the value field to a default.
Create a constructor that sets the value field to the passed parameter.
The following example shows the outline of a custom PortfolioHolder implementation class:
package com.flutebank.brokerage; public final class PortfolioHolder implements javax.xml.rpc.holders.Holder { public Portfolio value; public PortfolioHolder() { } // set the value variable to a default value } public PortfolioHolder(Portfolio value) { // set the value variable to the passed in value } }
Besides the data types supported by JAX-RPC discussed earlier, it may be necessary to pass data types that do not satisfy the requirements. For example, BillPay.java demonstrated earlier could define the listScheduledPayments() to return a java.util.Vector of PaymentDetail objects, instead of the PaymentDetail[] it did return. Note that a Vector is not a supported data type, as per the mappings in Table 10.2.
JAX-RPC supports the concept of pluggable serializers and deserializers for such custom data types. A serializer marshals a Java object to an XML representation, and a deserializer unmarshals an XML representation to a Java object. As Figure 10.11 shows, serialization and deserialization are symmetrical functions and both use type mapping to map the Java and XML data types.
Developers can specify the serializer and deserializer to use for a service on the server using the deployment tool. xrpcc has the typemapping element for this purpose. This allows the endpoint to unmarshal the XML to the corresponding Java type, and vice versa. For example, a com.fluebank.Vector may be serialized as
<avector xmlns:tns="http://www.flutebank.com" xsi:type="tns:Vector"> <item xsi:type="xsd:string">some value here</item> <item xsi:type="xsd:anyType" xsi:null="true"/> </avector >
If the server know that this namespace and type correspond to a com.flutebank.Vector, it can invoke the corresponding deserializer and create and pass the corresponding com.flutebank.Vector object to the service implementation. When the service client is written, the developers will need to write a similar serializer and deserializer on the client-side runtime or take a shortcut and use the same classes from the server, if the same vendor runtime is used. If it is not used, the runtime will not know what to do when it comes across this custom data type and will throw a serialization exception.
The JAX-RPC part of the API, the type system relevant to development of pluggable serializers and deserializers, is simple and is shown in Figure 10.12 and Table 10.10.
TypeMappingRegistry |
Defines an internal registry that holds a mapping of encoding styles and the corresponding TypeMapping. |
TypeMapping |
Maintains a set of tuples of the type {Java type, SerializerFactory, DeserializerFactory, XML type}. |
Serializer |
The base interface for serializers to implement. |
DeSerializer |
The base interface for deserializers to implement. |
SerializationContext |
Passed to the serializer as context information. |
DeSerializationContext |
Passed to the deserializer as context information. |
The base serializer and deserializer interfaces are implemented by a runtime-specific class or extended by a runtime-specific interface. Developers use this to write their serializers and deserializers for that particular runtime. However, a larger issue is at hand. A closer look at the Serializer, DeSerializer, SerializationContext, and DeSerializationContext interfaces reveals no methods relate to serialization or deserialization and that these are just marker interfaces. What this means is that serializers and deserializers are not guaranteed to be portable across implementations, because there is no contract with the runtime. They are specific to and pluggable only in a particular implementation. For example, if a developer writes a serializer and deserializer for the JAX-RPC RI, these classes are not guaranteed to be usable in another vendor's JAX-RPC implementation.
The API is structured like this for a very good reason. Different runtimes may (and do) use different XML parsing techniques (e.g., DOM parser, SAX parser, streaming pull). Porting a serializer written for SAX parsing (i.e., one that expects a SAX stream) into a runtime that uses a different parsing mechanism cannot be done completely transparently. The next version of the JAX-RPC specification is supposed to address transparent pluggability further.
Most vendors will provide several built-in serializers and deserializers, to help developers as utility classes for their runtimes. The code will never be aware of the need for a serializer/deserializer for that particular custom data type, as long the code is deployed in that vendor's runtime. (If you move it to another, you may need to write the serializer and deserializer yourself.) For example, JAX-RPC 1.0 RI supports a subset of Java collection classes and provides corresponding serializers and deserializers as utilities for developers (Table 10.11).
java.util.Collection java.util.List java.util.Set java.util.Vector java.util.Stack java.util.LinkedList java.util.ArrayList java.util.HashSet Java.util.TreeSet Java.util.Map Java.util.HashMap Java.util.TreeMap Java.util.Hashtable java.util.Properties |
So if the listScheduledPayments() method returned a java.util.ArrayList, even though it is not a data type for which a standard Java-XML mapping exists, the runtime will generate the corresponding SOAP message and response, based on internal type mapping and custom serializers and deserializers.
Like message handlers, pluggable serializers and deserializers can be configured in two ways: programmatically, using JAX-RPC API, or declaratively, using a JAX-RPC runtime-provided tool or deployment descriptor. Client-side serializers and deserializers can be configured in either way, but server-side handlers can be be configured only declaratively. The fragment below shows the relevant extract for xrpcc in the reference implementation given in Listing 10.3.
<typeMappingRegistry> <import> <schema namespace=""location=""/> </import> <typeMapping encodingStyle=""> <entry schemaType=""javaType=""serializerFactory=""deserializerFactory=""/> </typeMapping> <additionalTypes> <class name=""/> </additionalTypes> </typeMappingRegistry>
Programmatic registration on the client can be done in code similar to the following:
ServiceFactory factory = ServiceFactory.newInstance(); Service service = factory.createService (...); TypeMappingRegistry registry = service. getTypeMappingRegistry(); TypeMappingRegistry maprping = registry.createTypeMapping() // or registry.getDefaultTypeMapping(); SerializerFactory sfactory= // some runtime specific code DeserializerFactory dfactory= // some runtime specific code // register the custom handlers passing the Java class, the namespace, the serializer // factory to use and the deserializer factory to use mapping.register(myclass, qname, sfactory, dfactory) registry.register(encodingStyleURI,mapping)
Security has multiple aspects; Chapter 15 covers them in detail. From a JAX-RPC perspective, there are two major points:
Securing the transport layer. JAX-RPC does not explicitly require runtimes to support Hypertext Transfer Protocol Secure (HTTPS). However most servlet containers where the HTTP endpoints are deployed potentially support HTTPS (e.g., Tomcat). Just switching on HTTPS on the server will be enough for the service deployment. To enable SSL support on the client side, however, JSSE must be used to change the default HTTP handlers.
Securing users. JAX-RPC requires support for basic HTTP authentication. The username and password on the client side can be passed via the javax.xml.rpc.security.auth.username and javax.xml.rpc.security.auth.username.password properties discussed in the Clients Using Stubs section earlier in this chapter. If at runtime the username or password is not found or is incorrect, the server code will send the client an HTTP code 401 along with the basic HTTP authentication header (WWW-Authenticate). In the service implementation, the service can access the java.security.Principal via the getUserPrincipal() in the ServletEndpointContext, as shown earlier.