In Section 6.6, earlier in this chapter, you saw that there are two different sets of encoding rules that are commonly used when creating SOAP messages — SOAP section 5 encoding, which is typically used for RPC-style operations, and literal encoding, which is the usual choice for a document-style operation. In order to create a SOAP message from the name and parameters of a method call or from its return value and output parameters, the JAX-RPC runtime has to know how to convert the Java primitive types and the Java objects used in the method definition into the corresponding XML representation that will appear in the message. The process of converting a Java type to its XML representation is called serialization, and the reverse process is termed deserialization. A class that can perform these conversions is called a serializer or a deserializer. In practice, both the serialization and deserialization rules for a specific type are implemented in the same class, which I will refer to simply as a serializer. In this section, we look at how JAX-RPC handles the serialization and deserialization processes. In case you think that you don't really need to know much about this somewhat esoteric issue, in the course of this discussion you'll see that you can't always assume that JAX-RPC will arrange for all of the serializers that you require to be available at runtime. This section shows you how to make sure that they are.
The JAX-RPC runtime stores information relating to serialization in a TypeMappingRegistry. There is a separate TypeMappingRegistry for each service endpoint being used by a client application or hosted by a servlet or session bean. On the client side, the TypeMappingRegistry is associated with the Service object. Client code can obtain a reference to it using the Service getTypeMappingRegistry( ) method. On the server side, it is owned and initialized by the tie class generated by wsdeploy (JWSDP) or deploytool (J2EE 1.4) and is not accessible to the web service implementation class. The relationship between the Service object, a server-side tie, and the TypeMappingRegistry, together with the objects within the registry, is shown in Figure 6-8.
A TypeMappingRegistry contains zero or more TypeMapping objects, each of which manages the serializers that encapsulate the encoding rules for a particular encoding style. The TypeMapping object for a given encoding style can be obtained by calling the TypeMappingRegistry getTypeMapping( ) method and supplying the URI that uniquely identifies the encoding style, provided that the TypeMappingRegistry has been initialized with a TypeMapping for that URI. The JAX-RPC reference implementation creates registries that are preconfigured with TypeMappings for the SOAP section 5 encoding rules and for literal encoding. Given a Service object, the following code can be used to get the TypeMapping for each of these encoding styles:
Service service = ......; // Get Service object TypeMapping soapMapping = service.getTypeMappingRegistry( ).getTypeMapping( javax.xml.rpc.NamespaceConstants.NSURI_SOAP_ENCODING)); TypeMapping literalMapping = service.getTypeMappingRegistry( ). getTypeMapping("");
The serializers and deserializers required for each encoding style are registered with the TypeMapping object using the register( ) method:
public void register(Class javaType, QName xmlType, SerializerFactory sf, DeserializerFactory dsf);
SerializerFactory and DeserializerFactory are interfaces implemented by classes that can return a serializer or deserializer that can map between the given Java type and XML type. A TypeMapping stores factory references instead of instances of the actual serializers so that the factory can determine when it is appropriate to create serializer instances. In the reference implementation, these factories create only a single instance of each serializer and deserializer, thereby minimizing object creation overhead as well as memory utilization.
When a serializer to map from a given Java type to a given XML type is required, the TypeMapping getSerializer( ) method is called:
public SerializerFactory getSerializer(Class javaType, QName xmlType);
If there is no factory registered for this combination of types, then null is returned. Once a factory is obtained, the serializer itself can be retrieved using the SerializerFactory getSerializerAs( ) method:
public Serializer getSerializerAs(String mechanismType);
The mechanismType argument is provided to allow serializers and deserializers to be developed that work with different underlying XML processing mechanisms. A deserializer factory may be able to provide deserializers that work with the input from any or all of a DOM model, a SAX event stream, or a streaming pull parser (i.e., a parser that processes XML elements on request by application code, rather than by delivering events or providing a complete document model). The current version of the JAX-RPC specification does not specify any fixed values for the mechanismType argument, and the reference implementation supports only one mechanism type that corresponds to a streaming pull parser. The same arrangement exists for deserializers.
The javax.xml.rpc.encoding.Serializer and javax.xml.rpc.encoding.Deserializer interface must be implemented by all serializers and deserializers, respectively. They are defined as follows:
public interface Serializer { public String getMechanismType( ); } public interface Deserializer { public String getMechanismType( ); }
The getMechanismType( ) method returns the identifier for the XML processing mechanism that the serializer or deserializer supports. Surprisingly, these interfaces do not include methods that actually perform any serialization or deserialization! The reason for this is that the current version of the JAX-RPC specification provides only a framework within which serializers and deserializers can be registered and located on demand. It does not specify how these objects are to be implemented nor how they are called, leaving these details to be determined by JAX-RPC vendors. In the future, the JAX-RPC specification is likely to be extended to allow developers to create serializers and deserializers that are portable between different vendors' JAX-RPC implementations.
For an example of the effect of the encoding style on the content of a SOAP message, consider how a date might be mapped to XML. The standard JAX-RPC mapping for the Java Date class (and also for Calendar) is to map to the XML Schema type xsd:dateTime. The standard serializer that maps from Date to xsd:dateTime included in the TypeMapping object for the SOAP section 5 encoding rules creates XML that looks like this:
<ns1:time xsi:type="xsd:dateTime">2002-10-25T19:53:53.250+01:00</ns1:time>
In this case, time is a type defined in the WSDL specification of a service to contain a value of type xsd:dateTime. If literal encoding is used, the XML is slightly different:
<ns1:time>2002-10-25T19:53:53.250+01:00</ns1:time>
Since these are relatively cumbersome representations, if you implement a web service intended for use in a closed environment, you might agree with the users of your service that dates could be encoded in a more lightweight form, perhaps as the number of milliseconds since January 1, 1970:
<ns1:time>1035657051647</ns1:time>
In order to do this, you would introduce a new serializer and deserializer for the Java Date and Calendar classes and plug them into the TypeMapping object for the SOAP-encoding style and/or the literal-encoding style. At the present time, however, it is not possible to write a custom serializer, either to change the way in which a supported type is serialized or to provide serialization for application-specific types, without becoming tied to a specific vendor's product. Therefore, it is best, wherever possible, to create web service interfaces that require only those types for which support is provided by the JAX-RPC specification.[14]
[14] The lack of a specification for portable serializers also means that you cannot implement a custom-encoding style without making assumptions about the serialization framework of a particular JAX-RPC implementation, since a new encoding style would require serializers and deserializers that implement the new encoding rules.
When the tie class for a server-side web service implementation class is initialized or a client application obtains a Service object for a web service endpoint, the TypeMappingRegistry associated with that endpoint is initialized. The registry content is constructed as follows:
A standard set of mappings is installed for both the SOAP and literal encodings. All JAX-RPC implementations are required to provide serializers and deserializers for the Java primitive types and the object types listed in Section 2.2.1.2. The JAX-RPC reference implementation also includes serializers and deserializers for some of the Java collection classes.
On the server side, the tie class installs serializers and deserializers for arrays and value types used in the service endpoint interface, as well as for the SOAP messages that will exchanged with the client. These serializers and deserializers are generated by wsdeploy and included in the deployable WAR file.
The same set of serializers is installed on the client side by the Service class generated by wscompile.
For an example of what might be included in the TypeMappingRegistry for a given service endpoint interface, consider the BookQuery interface defined for the book web service used in Chapter 2, which is shown in Example 6-56.
public interface BookQuery extends Remote { public abstract int getBookCount( ) throws RemoteException; public abstract String getAuthor(String name) throws RemoteException; public abstract String getEditor(String name) throws RemoteException; public abstract double getPrice(String name) throws BookServiceException, RemoteException; public abstract BookInfo[] getBookInfo( ) throws RemoteException; public abstract HashMap getBookMap( ) throws RemoteException; }
The registry will need serializers and deserializers for the SOAP request and response messages for each method call, which will themselves need to use lower-level serializers that can handle the data types used as method call arguments and return values. In the case of this interface, the data types concerned are as follows:
int
double
String
HashMap
BookInfo
BookInfo[]
The first three of these are standard types for which all JAX-RPC implementations are required to provide serializers, whereas HashMap is supported as an extension by the JAX-RPC reference implementation. The serializers and deserializers for these four types therefore appear in the TypeMappingRegistry when it is initialized. The BookInfo class is a value type that contains values that are directly supported by JAX-RPC. A custom serializer is generated for this class by both wscompile and wsdeploy.[15] Similarly, a serializer for an array of BookInfo objects is generated. These custom serializers are registered by the Service object on the client side and by the tie class on the server side.
[15] Of course, a deserializer for this type is also generated. For the sake of brevity, from this point forward, I'll use the term "serializer" as shorthand for both serializer and deserializer, unless otherwise stated.
Both wscompile and wsdeploy ensure that the registry contains serializers for all of the types that are used in the Java interface or WSDL document that defines the service endpoint interface for a web service. Sometimes, however, this is not sufficient. Some examples of instances in which these tools cannot generate all of the serializers that are required at runtime, given only the information in the WSDL document or the Java interface, are described in the following list.
The JAX-RPC reference implementation can serialize and deserialize a subset of the Java collection classes from the java.util package. These serializers know how to create XML that represents the structure of the collection, including the pairing of keys with values for collections such as HashMap. However, it is still necessary for the registry to contain specific serializers for the objects that are stored in the collection. This requirement is met if the collection contains only the standard JAX-RPC types such as String, Integer, Date, etc. It is also met for objects that are used elsewhere in the interface or WSDL definition from which the stubs and ties for the service are generated. In the case of the BookQuery interface, for example, the HashMap returned by the getBookMap( ) method contains only Strings (for the entry keys) and BookInfo objects (for the entry values). String is a JAX-RPC supported type, while BookInfo is explicitly referenced elsewhere in the interface definition—therefore, both of these types already have serializers installed. This requirement would not be met if the collection contained an instance of a class that does not appear in the interface definition (even if that class is a value type that could be serialized by JAX-RPC), or if it contained an array of objects of any type—even primitive objects for which serializers are already registered (such as String[ ]).
Suppose that the provider of the web service that uses the BookQuery interface decides to include a new category of books that could be viewed over the Internet by registered subscribers. To accommodate this, a subclass of BookInfo, called ElectronicBookInfo, might be developed that includes the URL at which the book could be found. The methods of the BookQuery interface would continue to refer to BookInfo throughout, since they would need to handle any kind of book. However, since ElectronicBookInfo is not mentioned in the interface definition, there is not a serializer for it.
Put in a more general form, this issue arises when there is a collection class in the interface definition, or when the Java method declarations or the WSDL document refer to base classes (perhaps abstract base classes) or interfaces, in which the objects that are actually transferred are instances of derived classes or classes that implement the specified interface. If the actual types are not mentioned anywhere in the endpoint interface definition, neither wscompile nor wsdeploy generates serializers for them.
Suppose that we were to extend the book web service from Chapter 2 by adding a URL for those books that can be viewed over the Internet, as previously suggested. We do this using the class ElectronicBookInfo, the definition of which is shown in Example 6-57.
public class ElectronicBookInfo extends BookInfo { private String url; /** * Constructs an <code>ElectronicBookInfo</code> object. * This constructor is used for deserialization. */ public ElectronicBookInfo( ) { } /** * Constructs an <code>ElectronicBookInfo</code> object initialized * with given attributes. */ public ElectronicBookInfo(String title, String author, String editor, double price, String url) { super(title, author, editor, price); this.url = url; } public String getURL( ) { return url; } public void setURL(String url) { this.url = url; } }
The addition of this subclass does not, of course, result in any changes in the BookQuery interface (which is shown in Example 6-56). As far as the results of calling the methods of this interface are concerned, the introduction of this BookInfo subclass has the following effect:
The array of BookInfo objects returned by the getBookInfo( ) method will be populated with BookInfo objects for those books that cannot be viewed on the Internet, and with ElectronicBookInfo objects for those that can.
Similarly, in the HashMap returned by getBookMap( ), in which the keys are the book titles, the value associated with each book is either a BookInfo or an ElectronicBookInfo object.
The client code shown in Example 6-58 could be used to fetch the books from the web service using both methods, and prints the results.
// Get all of the books BookInfo[] info = bookQuery.getBookInfo( ); System.out.println("Books from getBookInfo( )"); for (int i = 0; i < info.length; i++) { boolean eBook = info[i] instanceof ElectronicBookInfo; System.out.println("\tTitle: " + info[i].getTitle( ) + (eBook ? "; URL: " + ((ElectronicBookInfo)info[i]).getURL( ) : "; not E-Book")); } // Get the books in a HashMap HashMap map = bookQuery.getBookMap( ); System.out.println("Books from getBookMap( )"); Iterator iter = map.values( ).iterator( ); while (iter.hasNext( )) { BookInfo book = (BookInfo)iter.next( ); boolean eBook = book instanceof ElectronicBookInfo; System.out.println("\tTitle: " + book.getTitle( ) + (eBook ? "; URL: " + ((ElectronicBookInfo)book).getURL( ) : "; not E-Book")); }
Notice that after calling each method, the runtime type of each BookInfo object is checked to determine whether it is accessible over the Internet. If we modified the servant for the book web service to include a mixture of books that are and are not accessible over the Internet, and then run this client code, we get output like that shown here (some lines have been omitted for the sake of brevity):
Books from getBookInfo( ) Title: Java in a Nutshell; not E-Book Title: J2ME in a Nutshell; not E-Book Title: Java Swing; not E-Book . . Title: Java Internationalization; not E-Book Books from getBookMap( ) Title: Java in a Nutshell; not E-Book Title: J2ME in a Nutshell; not E-Book . . Title: Java Internationalization; not E-Book . . Title: Java Swing; not E-Book
These results indicate that all of the objects returned were of type BookInfo, despite the fact that the server has a mixture of BookInfo and ElectronicBookInfo objects. This is perhaps not surprising, since neither the client nor the server have a serializer for the ElectronicBookInfo class (which is not explicitly mentioned in the BookQuery interface). As a result, the ElectronicBookInfo objects are all serialized as instances of the base class BookInfo, for which a serializer is available.
On the client side, you can arrange for wscompile to generate serializers for classes that do not appear in the service endpoint interface definition by including one or more additionalTypes elements in the config.xml file. Example 6-59 shows the config.xml file for a client that will access a version of the book web service that has been enhanced to return both BookInfo and ElectronicBookInfo objects.
<?xml version="1.0" encoding="UTF-8" ?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="SerializerBookService" targetNamespace= "urn:jwsnut.chapter6.serializerbookservice/wsdl/SBookQuery" typeNamespace= "urn:jwsnut.chapter6.serializerbookservice/types/SBookQuery" packageName="ora.jwsnut.chapter6.serializerbookservice"> <interface name="ora.jwsnut.chapter6.serializerbookservice.SBookQuery"/> <typeMappingRegistry> <additionalTypes> <class name= "ora.jwsnut.chapter6.serializerbookservice.ElectronicBookInfol"/> </additionalTypes> </typeMappingRegistry> </service> </configuration>
The typeMappingRegistry element shown here has several possible child elements that can be used to specify the content of the TypeMappingRegistry associated with the Service object being configured, all of which are described in Chapter 8. The additionalTypes element lists the classes for which serializers must be generated and added to the registry at runtime. Each such class requires a class element with a name attribute set to its fully qualified name.
A serializer for the ElectronicBookInfo class must also be available to the JAX-RPC runtime on the server side. However, the schema definition for the jaxrpc-ri.xml file, which wsdeploy uses to determine the tie classes and serializers that it needs to generate, does not include a typeMappingRegistry element. The only way to communicate a requirement for additional serializers to wsdeploy is to generate a model file from the information contained in the config.xml file and then include a reference to it in the endpoint element for the service. This is the same technique that we used when it was necessary to arrange for wsdeploy to generate ties for a document-style service endpoint interface in Section 6.6, earlier in this chapter. The model file is created by supplying the -model argument to wscompile when creating the client-side artifacts, and is then placed in the WEB-INF directory of the portable WAR file used by wsdeploy, along with a suitably modified jaxrpc-ri.xml file, as shown in Example 6-60.
<?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.serializerbookservice/wsdl/"
typeNamespaceBase="urn:jwsnut.chapter6.serializerbookservice/types/">
<endpoint
name="SBookQuery"
displayName="SBookQuery Port"
description="Serializer Book Query Port"
interface="ora.jwsnut.chapter6.serializerbookservice.SBookQuery"
model="/WEB-INF/model"
implementation=
"ora.jwsnut.chapter6.serializerbookservice.SBookServiceServant"/>
<endpointMapping
endpointName="SBookQuery"
urlPattern="/SBookQuery"/>
</webServices>
These changes make serializers for the types listed in the additionalTypes elements of the config.xml file available in the TypeMappingRegistry used by the server-side tie classes for this web service, as well as in the registry created by the generated Service object used on the client side. To run this service, make chapter6\serializerbookservice your current directory, and deploy it into your web container using the command:
ant deploy
Next, you need to compile and run the client application, which uses the code shown in Example 6-58, using the following commands:
ant compile-client ant run-client
Apart from a few lines left out for the sake of brevity, the output from the client looks like this:
Books from getBookInfo( ) Title: Java in a Nutshell; not E-Book Title: J2ME in a Nutshell; not E-Book Title: Java Swing; not E-Book . . Title: Java Internationalization; not E-Book Books from getBookMap( ) Title: Java in a Nutshell; URL: http://www.ora.com/catalog/javanut Title: J2ME in a Nutshell; URL: http://www.ora.com/catalog/j2meanut . . Title: Java Internationalization; not E-Book Title: Java Swing; not E-Book
It is obvious that the values returned from the getBookInfo( ) method do not match those in the HashMap obtained by calling getBookMap( ). The values in the HashMap are a mixture of ElectronicBookInfo (those where a URL is shown) and BookInfo objects (where there is no URL); these are the "correct" results because they match the values held at the server. However, the array returned by getBookInfo( ) contains only BookInfo objects — even those books that have associated URLs have been serialized and deserialized as BookInfo objects rather than ElectronicBookInfo objects. This difference arises from the way in which the values were serialized. Here are the rules that govern the way in which a serializer is chosen:
If the runtime type of the object cannot be fully resolved at compilation time, then an appropriate serializer is chosen at serialization time. This is the case when the type is declared to be java.lang.Object, which is an abstract type or a Java interface.
In all other cases, the serializer to be used is determined during the code generation process for the client-side stubs or server-side ties.
In the case of the getBookInfo( ) method, the return value is declared to be an array of BookInfo objects. Since BookInfo is a concrete class, the stubs and ties are generated in such a way that a serializer for BookInfo is used, even though the runtime type of any of the objects in the array could be either BookInfo or its subclass ElectronicBookInfo. By contrast, the values in a HashMap are of type java.lang.Object. Therefore, the serializer is chosen at runtime based on the actual type of each value in the collection. As a result, BookInfo objects are serialized using the BookInfo serializer, and ElectronicBookInfo objects by the ElectronicBookInfo serializer.
Had the getBookInfo( ) method been defined like this:
public Object[] getBookInfo( );
then the returned array would have contained a mixture of BookInfo and ElectronicBookInfo objects. Of course, replacing BookInfo[] with Object[] in this way is bad programming practice. A better alternative is to make BookInfo an abstract base class and then create two concrete subclasses:
public abstract class BookInfo {
// Same definition as existing BookInfo class
}
public class HardCopyBookInfo extends BookInfo {
// Nothing added
public HardCopyInfo( ) {
// Required default constructor
}
public HardCopyInfo(String title, String author, String editor,
double price) {
super(title, author, editor, price);
}
}
public class ElectronicBookInfo extends BookInfo {
// Defined as shown in Example 6-57
}
|
Once BookInfo is made abstract, the serialization of the BookInfo array returned by getBookInfo( ) uses serializers chosen at runtime. Naturally, it is also necessary to include HardCopyBookInfo in the additionalTypes list in config.xml so that a serializer for HardCopyBookInfo is created.