Monday Oct 01, 2007
Monday Oct 01, 2007
By Doug Kohlert
Java Architecture for XML Binding (JAXB) 2.1 introduced a new annotation, @XmlSeeAlso, that you can use to make JAXB aware of additional types. Java API for XML-Based Web Services (JAX-WS) 2.1 also uses the @XmlSeeAlso annotation to allow use of abstract classes in
a service endpoint interface (SEI). JAX-WS 2.1 allows you to specify the @XmlSeeAlso annotation on a SEI. JAX-WS reads this annotation at runtime making sure to pass all of the classes referenced by the annotation to JAXB via the JAXBContext. The use of the @XmlSeeAlso annotation in JAXB and JAX-WS enables support for type substitution, a subclassing concept that complements inheritance.
This tip will show you how to develop a simple web service that uses type substitution as well as a client that consumes the web service. You'll see how to build the web service from a Java class and from a WSDL file.
A sample application accompanies this tip. The code examples in the tip are taken from the source code of the sample application.
Using Type Substitution in a Web Service
Suppose you want to build a web service that manages the inventory for a store that sells wakeboards and related equipment. Wakeboards are short boards made of buoyant material that are used to ride over the surface of a body of water, typically behind a boat or with a cable-skiing apparatus.
For simplicity, let's assume that the store sells only three types of items: wakeboards, bindings, and towers for boats. You
want the web service to be fairly simple to use and have a minimal amount of exposed operations. So to keep things
simple, the web service uses an abstract Item class in its operations instead of using type-specific operations. The
following Item class can be used to model any inventory object that you might want to expose through your web service:
public abstract class Item implements Serializable { private long id; private String brand; private String name; private double price; ... }
Extending the Item class, you can define the following Wakeboard, WakeboardBinding and Tower classes:
public class Wakeboard extends Item { private String size; } public class WakeboardBinding extends Item { private String size; } public class Tower extends Item { private Fit fit; private String tubing; public static enum Fit { Custom, Exact, Universal }; }
Because this example is about type substitution, let's make the inheritance hierarchy a little more interesting by introducing
a Wearable abstract class. Wearable holds the size attribute for both the Wakeboard and WakeboardBinding classes. The Wearable class is defined as follows:
public abstract class Wearable extends Item { protected String size; }
And the resulting Wakeboard and WakeboardBinding classes are:
public class Wakeboard extends Wearable { } public class WakeboardBinding extends Wearable { }
Also, because the web service manages inventory, you'll want the inventory items to be persisted to a database using the Java Persistence API (sometimes referred to as JPA). To do this, you need to add an @Entity annotation to each of the classes that will be persisted. The only class that you probably don't want to persist is the Wearable class. You can add the @MappedSuperclass annotation to this class so that the JPA will use the attributes of this class for persisting subclasses. Next, you need to add the @Id and the @GeneratedValue(strategy = GenerationType.AUTO) annotations to the Item.Id field. As a result, the field will be used as the primary key in the database and the Id will be automatically generated if not provided. Finally, because you might add new types of Items into the system at a later time, you should add the @Inheritance(strategy=InheritanceType.JOINED) annotation to the Item class. This will store each subclass in its own database table.
The final data classes look like the following:
@Entity @Inheritance(strategy=InheritanceType.JOINED) public abstract class Item implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String brand; private String itemName; private double price; // Getters & setters ... } @MappedSuperclass public abstract class Wearable extends Item { protected String size; ... } @Entity public class Wakeboard extends Wearable {} @Entity public class WakeboardBinding extends Wearable {} @Entity public class Tower extends Item { private Fit fit; private String tubing; public static enum Fit { Custom, Exact, Universal }; ... }
Now that you defined the data model for the application, you can now define the web service interface. Because the application manages information about wakeboard equipment, let's call the web service WakeRider and let's expose four operations in the web service: addItem, updateItem, removeItem, and getItems.
Here is what the WakerRider class looks like:
@WebService() public class WakeRider { ... public List<Item> getItems() {...} public boolean addItem(Item item) {...} public boolean updateItem(Item item) {...} public boolean removeItem(Item item) {...} }
If you deployed this web service and then looked at the generated WSDL and schema, you would notice that only the Item type is defined -- there is no mention of Wearable, Wakeboard, WakeboardBinding, or Tower. This is because when JAX-WS introspects the WakeRider class there is no mention of the other classes. To remedy that you can use the new @XmlSeeAlso annotation and list the other classes that you want to expose through the WakeRider web service.
Here is what the WakeRider class looks like with the @XmlSeeAlso annotation:
@WebService() @XmlSeeAlso({Wakeboard.class, WakeboardBinding.class, Tower.class}) public class WakeRider { ... }
Now when you deploy the WakeRider service and look at the generated schema, you will see types for Item, Wearable, Wakeboard, WakeboardBinding, and Tower as well as some other types used internally by JAX-WS and JAXB.
Starting From WSDL
You can use type substitution in a web service that is built from a WSDL file. What's particularly nice about this is that
using type substitution when starting from WSDL is totally transparent. When you import a WSDL file with JAX-WS 2.1, the
generated proxy class is required to have the appropriate @XmlSeeAlso annotation. For example, the imported WakeRider proxy from the web service example in the previous section would have an @XmlSeeAlso annotation like the following:
@WebService(name="WakeRider", targetNamespace="http://wakerider/") @XmlSeeAlso({ObjectFactory.class}) public interface WakeRider { ... }
Notice that the @XmlSeeAlso annotation in the proxy contains the ObjectFactory.class instead of listing the classes. The ObjectFactory class is a JAXB required class that provides information about all of the Java types that JAXB needs to be aware of in the given package. In this example, the ObjectFactory class will have references to the Item, Wearable, Wakeboard, WakeboardBinding and Tower classes. There is nothing that you need to do to enable type substitution when starting from WSDL.
The WakeRider Client
Invoking the WakeRider web service from a client is the same as invoking any other web service using JAX-WS. All you need to do is get a WakeRider proxy from the generated WakeRider web service and invoke the operations on the proxy. The sample application that accompanies this tip contains a NetBeans 5.5.1 project for a Java Platform, Standard Edition (Java SE) application named wrmanager. You can use the application to add, remove, or edit items in the WakeRider web service inventory.
There is also a NetBeans 5.5.1 project for a JavaServer Faces (JSF) technology application named wrviewer. The application
uses the WakeRider web service to view the current inventory.
Both of these client applications contain code similar to the following for invoking an operation on the WakeRider web service:
WakeRiderService service = new WakeRiderService(); port = service.getWakeRiderPort(); List<Item> items = port.getItems(); for (Item item : items) { if (item instanceof Wakeboard) { ... } else if (Item instance of WakeboardBinding) { ... } else if (Item instance of Tower) { ... } }Running the Sample Code
The sample code for this tip is available as three NetBeans projects:
wrservice. Defines the WakeRider endpoint.wrviewer. A JSF page for viewing the WakeRider inventory.wrmanager. A Java SE application for adding, removing, and editing items in the WakeRider.You can build and run the sample code using the NetBeans 5.5.1 IDE as follows:
<sample_install_dir>/wakerider, where <sample_install_dir> is the directory where you installed the sample application. For example, if you extracted the contents to C:\ on a Windows machine, then your newly created directory should be at C:\wakerider. The wakerider directory contains one directory for each of the NetBeans projects: wrservice, wrviewer, and wrmanager.wrservice project as follows:wrservice directory from the sample application download.wrservice node in the Projects window and selecting Resolve Missing Server Problem. Then select Sun Java System Application Server.wrservice project as follows:wrservice node in the Projects window.wrviewer project as follows:wrviewer directory from the sample application download.wrviewer as follows:wrviewer node in the Projects window.WakeRider inventory. The inventory should be empty the first time you run wrviewer.
WakeRider Inventory
|
wrmanager project as follows:wrmanager directory from the sample application download.wrmanager as follows:wrmanager node in the Projects window.
WakeRider Inventory Manager
|
WakeRider Inventory
|
About the Author
Doug Kohlert is a senior staff engineer in the Web Technologies and Standards division of Sun Microsystems where he is the specification lead for JAX-WS.