Using Type Substitution with Web Services

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:

  1. If you haven't already done so, download and install the NetBeans 5.5.1 IDE.

  2. If you haven't already done so, download and install GlassFish V2 RC 4 or later.

  3. Download the sample application for the tip and extract its contents. You should now see the newly extracted directory as <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.

  4. Start the NetBeans IDE. Run Netbeans with JDK 5.0. You can also use JDK 6, however in that case, you will also need to follow the instructions in Running on top of JDK 6.

  5. Add GlassFish V2 to the NetBeans Application Servers as follows:

    • Right click on Servers node in the Runtime window.
    • Select Add Server.
    • Leave the Server as Sun Java System Application Server.
    • Click the Next button.
    • Click the Browse button and browse to the location that you installed GlassFish V2.
    • Click the Choose button.
    • Click the Next button.
    • Set the Admin Password to the default, adminadmin, unless you chose a different password for GlassFish.
    • Click the Finish button.

  6. Open the wrservice project as follows:

    • Select Open Project from the File menu.
    • Browse to the wrservice directory from the sample application download.
    • Click the Open Project Folder button.
    • If you are alerted to a "Missing Server Problem", resolve it by right clicking on the wrservice node in the Projects window and selecting Resolve Missing Server Problem. Then select Sun Java System Application Server.

  7. Deploy the wrservice project as follows:

    • Right click the wrservice node in the Projects window.
    • Select Deploy Project.

  8. Open the wrviewer project as follows:

    • Select Open Project from the File menu.
    • Browse to the wrviewer directory from the sample application download.
    • Click the Open Project Folder button.
    • You may need to resolve a missing server problem as described in step 6.

  9. Run wrviewer as follows:

    • Right click on the wrviewer node in the Projects window.
    • Select Run Project. This should open a window in your web browser that displays the current WakeRider inventory. The inventory should be empty the first time you run wrviewer.

    Wake Rider Inventory
    WakeRider Inventory
     
  10. Open the wrmanager project as follows:

    • Select Open Project from the File menu.
    • Browse to the wrmanager directory from the sample application download.
    • Click the Open Project Folder button.

  11. Run wrmanager as follows:

    • Right client on the wrmanager node in the Projects window.
    • Select Run Project. This should open the WakeRider Inventory Manager application.

    WakeRider Inventory Manager
    WakeRider Inventory Manager
     
  12. Add, delete, edit, or view inventory items as follows:
  • To add an item, click the Add button in the WakeRider Inventory Manager application, fill in the Add Item dialog and click the OK button.

  • To edit an item, select the item in the appropriate inventory window in the WakeRider Inventory Manager application and click the Edit button. Modify the contents of the Edit Item dialog and click the OK button.

  • To delete an item, select the item in the appropriate inventory window in WakeRider Inventory Manager application and click the Remove button.

  • To view current inventory items in the wrmanager application, view or refresh the wrviewer page in your browser.

  • WakeRider Inventory
    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.

Comments:

Thanks for the article on this topic. I tried and it works. But it looks like it works for Document Wrapper but not for Document Bare. Is there any other solution for document bare to make it work? I get the following error if I use document bare for this scenario.
Unable to create an instance of org.mydemo.Item which is abstract.
Thanks for your help.
Ramesh

Posted by Ramesh Nune on December 29, 2009 at 11:37 PM PST #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today