 | Level: Intermediate Jeffrey Liu (jeffliu@ca.ibm.com), Software Developer, IBM
16 Jul 2004 This article demonstrates how to use the Eclipse Modeling Framework (EMF) to enhance the JAX-RPC type mapping model. It also provides code samples to guide you through the creation of a Web service from a Web Services Description Language (WSDL) document that uses JAX-RPC unsupported XML data types.
Introduction
JAX-RPC, also known as JSR-101, is an important step forward in achieving a standard programming model that facilitates the building of interoperable Web services on the Java™ platform. A key aspect of JAX-RPC is its XML to Java type mapping model, which serves as an implementation standard for Web services product providers. Without such a model, Web services product providers might fall into the trap of defining proprietary type mappings, which can be a serious source of Java interoperability problems.
Although JAX-RPC devotes a considerable amount of effort in supporting XML data types, room for improvement still exists. Moreover, JAX-RPC requires the mapping of any unsupported XML data types to the javax.xml.soap.SOAPElement interface. This interface does not provide a strongly typed Java model for users to work with, meaning they might have to write custom code to parse through a SOAPElement instance. This might not be intuitive for novice users, especially when dealing with large XML fragments. This article demonstrates how to use EMF to support XML data types that do not have standard JAX-RPC type mappings. Generating a Web service that has substantial usage of JAX-RPC unsupported XML data types is not easy, but this article provides you with an effective solution by combining the use of Web services tools and EMF tools in IBM® WebSphere® Studio Application and Site Developer V5.1.2 (Application and Site Developer).
Create the supply-chain Web service
To follow this article, you must install WebSphere Application and Site Developer V5.1.2. If necessary, you can download a 60-day trial version.
- Create a Web project. Click on menu File > New > Project... > Web >
Dynamic Web Project > Next to open the New Dynamic Web Project wizard.
- Enter SupplyChainWeb as the Web project name, select the Configure advance options checkbox and
click Next.
- Enter SupplyChainEAR as the EAR project name and click Finish.
- Click on the Code icon at the top of this article and download SupplyChainService.wsdl and SupplyChainSchema.xsd
to your file system.
- Import or copy SupplyChainService.wsdl and SupplyChainSchema.xsd into the root of the SupplyChainWeb project.
- In the navigator view, right click on SupplyChainService.wsdl > Web Services > Generate Java bean skeleton to open the WSDL to Java Bean Skeleton wizard Figure 1 shows. This wizard generates a Java skeleton code implementation based on the information defined in the WSDL document. Accept the default settings and click Finish.
Figure 1. WSDL to Java Bean Skeleton wizard
- After the wizard completes, you see a few WSDL validation errors in the tasks view because the XML schema file, SupplyChainSchema.xsd, was not copied to the proper places. To fix these errors, copy SupplyChainSchema.xsd from the root of the SupplyChainWeb project to the /SupplyChainWeb/WebContent/WEB-INF/wsdl/ directory and the /SupplyChainWeb/WebContent/wsdl/com/example/supplychain/www/ directory. Rerun validation by right clicking on SupplyChainService.wsdl > Run validation.
Create the supply-chain EMF model
The WSDL to Java Bean Skeleton wizard generated JavaBeans with one or more properties mapped to SOAPElement (specifically, PurchaseOrderType.java, PurchaseReferenceType.java, and ShippingNoticeType.java). In this section, you generate an EMF model for the supply-chain Web service to support the XML data types mapped to SOAPElement.
- Create an EMF project. Click on menu File > New > Project... > Eclipse Modeling
Framework > EMF Project > Next to open the New EMF Project wizard.
- Enter SupplyChainEMF as the project name and click Next.
- Select Load from an XML schema
and click Next.
- Click Browse Workspace... to open the file selection dialog. Find and select SupplyChainSchema.xsd and click OK.
Click Next.
- Select the supplychain package and click Finish. Refer to Figure 2.
Figure 2. New EMF Project wizard
- After the New EMF Project wizard completes, the EMF Generator Editor opens. In this editor, right click on the SupplyChainSchema node > Generate Model Code. You've successfully generated the supply-chain EMF model. In the next section, you learn how to integrate the EMF code into the supply-chain Web service.
Integrate the supply-chain Web service and EMF model
- Set up all the dependencies for the SupplyChainWeb project.
Add the SupplyChainEMF project to the SupplyChainEAR as a utility JAR file and specify a JAR file dependency from the SupplyChainWeb
project to this utility JAR file.
- Open /SupplyChainEAR/META-INF/application.xml in the Application Deployment Descriptor Editor. Click on the Module tab.
- In the Project Utility JARs section, click Add..., select SupplyChainEMF and click Finish. Save and close the Application Deployment Descriptor Editor.
- Open /SupplyChainWeb/WebContent/META-INF/MANIFEST.MF in the JAR Dependency Editor. In the Dependencies section, select SupplyChainEMF.jar. Save and close the JAR Dependency Editor.
- Add the EMF libraries to the Java build path of the SupplyChainWeb project.
Right click on the SupplyChainWeb project > Properties > Java Build Path. Click on
the Libraries tab > Add Variable....
- Select EMF_COMMON, EMF_ECORE and
EMF_ECORE_XMI. Click OK > OK.
- Listing 1 shows all the import statements you use. Open /SupplyChainWeb/JavaSource/com/example/supplychain/www/SupplyChainBindingImpl.java in the Java Editor and add these import statements.
Listing 1. Import statements
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import com.example.supplychain.ItemType;
import com.example.supplychain.PaymentMethodType;
import com.example.supplychain.ProcessingType;
import com.example.supplychain.ShippingItemType;
import com.example.supplychain.StatusType;
import com.example.supplychain.SupplychainFactory;
import com.example.supplychain.SupplychainPackage;
import com.example.supplychain.impl.SupplychainPackageImpl;
import com.example.supplychain.util.SupplychainResourceFactoryImpl;
|
- You must initialize the generated supply-chain EMF model before you use it. The initialization process builds a mapping between elements declared in the XML schema (SupplyChainSchema.xsd) and Java classes that the EMF code generator created. This mapping serves as the recipe for converting XML fragments to the corresponding EMF-based Java classes and vice versa. To initialize the supply-chain EMF model, add the following static block to SupplyChainBindingImpl.java.
Listing 2. Initializing the EMF package
static
{
SupplychainPackageImpl.init();
}
|
- Next, you add four methods to SupplyChainBindingImpl.java that convert a SOAPElement to a DOMElement, and then a DOMElement to the corresponding EMF-based Java class and vice versa. Listings 3, 4, 5, and 6 show these methods. The
soapElement2DOMElement(SOAPElement soapElement) method and the domElement2SOAPElement(Element e) method, which are responsible for the SOAPElement to DOMElement conversion, utilize a couple of proprietary methods that are specific to WebSphere Application Server V5.x. Unfortunately at this time, there's no public methods for such conversion. To avoid using version specific code, you can manually traverse the SOAPElement and build the DOMElement accordingly.
In the future, when a SOAP with Attachments API for Java V1.2 (SAAJ)-compatible implementation becomes available, you no longer need to use version specific code to do SOAPElement to DOMElement conversion because SAAJ V1.2 requires SOAPElement to extend DOMElement directly.
Listing 3. Converting a SOAPElement to a DOMElement
public Element soapElement2DOMElement(SOAPElement soapElement)
throws Exception
{
return ((com.ibm.ws.webservices.engine.xmlsoap.SOAPElement)soapElement).getAsDOM();
}
|
Listing 4. Converting a DOMElement to an EMF object
public EObject domElement2EObject(Element e)
throws TransformerConfigurationException, TransformerException, IOException
{
DOMSource domSource = new DOMSource(e);
Transformer serializer = TransformerFactory.newInstance().newTransformer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.transform(domSource, new StreamResult(baos));
byte[] b = baos.toByteArray();
System.out.println(new String(b));
URI uri = URI.createURI(SupplychainPackage.eNS_URI);
SupplychainResourceFactoryImpl factory = new SupplychainResourceFactoryImpl();
Resource res = factory.createResource(uri);
ByteArrayInputStream bais = new ByteArrayInputStream(b);
res.load(bais, null);
List contents = res.getContents();
return (!contents.isEmpty()) ? (EObject)contents.get(0) : null;
}
|
Listing 5. Converting an EMF object to a DOMElement
public Element eObject2DOMElement(EObject eObject)
throws IOException, ParserConfigurationException, SAXException
{
URI uri = URI.createURI(SupplychainPackage.eNS_URI);
SupplychainResourceFactoryImpl factory = new SupplychainResourceFactoryImpl();
Resource res = factory.createResource(uri);
res.getContents().add(eObject);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
res.save(baos, null);
byte[] b = baos.toByteArray();
System.out.println(new String(b));
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new ByteArrayInputStream(b));
return doc.getDocumentElement();
}
|
Listing 6. Converting a DOMElement to a SOAPElement
public SOAPElement domElement2SOAPElement(Element e)
throws SOAPException
{
SOAPFactory soapFactory = SOAPFactory.newInstance();
com.ibm.ws.webservices.engine.xmlsoap.SOAPElement soapElement =
(com.ibm.ws.webservices.engine.xmlsoap.SOAPElement)soapFactory.createElement(
"temp");
soapElement.setAlternateContent(e);
return soapElement;
}
|
Global vs. local elements
As mentioned earlier, the supply-chain EMF model relies on the element to Java mapping to convert an XML fragment to the corresponding EMF-based Java class. However, by default, the EMF code generator only generates mapping entries for global elements, not for local elements. Global elements are element declarations that appear as children of the schema element in an XML schema document, whereas local elements are not. The default list of mappings excludes local elements, and therefore, the supply-chain EMF model can't convert an XML fragment that represents an instance of a local element. Consider the sample XML schema Listing 7 shows. The corresponding EMF model recognizes the global element instance Listing 8 shows. Conversely, the local element instance Listing 9 shows results in an exception. To support conversion of local elements, you must add custom elements to Java mappings.
Listing 7. Sample XML schema
<schema>
<element name="GlobalElement">
<complexType>
<sequence>
<element name="LocalElement" type="xsd:string"/>
</sequence>
</complexType>
</element>
</schema>
|
Listing 8. GlobalElement instance
<GlobalElement>
<LocalElement>Some String</LocalElement>
</GlobalElement>
|
Listing 9. LocalElement instance
<LocalElement>Some String</LocalElement>
|
- When you look at the SupplyChainSchema.xsd document and the JavaBeans that the WSDL to Java Bean Skeleton wizard generated, you see three local elements that are mapped to SOAPElement:
<paymentMethod> element from the <PurchaseOrderType> complex type
<item> element from the <PurchaseOrderType> complex type
<item> element from the <ShippingNoticeType> complex type
To set up a custom mapping between the <paymentMethod> local element and the com.example.supplychain.PaymentMethodType Java class, open /SupplyChainEMF/src/com/example/supplychain/impl/SupplychainPackageImpl.java in the SupplyChainEMF project. Add the code snippet from Listing 10 to the end of the initializePackageContents() method. This method will be called as part of the initialization.
Listing 10. Adding a local element mapping
initEClass(paymentMethodTypeEClass, PaymentMethodType.class,
"paymentMethod", !IS_ABSTRACT, !IS_INTERFACE);
|
- Next, you set up custom mappings for the two
<item> local elements. Unlike the <paymentMethod> element, you cannot add a static mapping entry in the initializePackageContents() method because the EMF model only allows one mapping for each local element name. To overcome this problem, you dynamically register and remove the necessary mappings as you use them. Listing 11 shows four methods that allow you to register and remove the <item> element mapping from the <PurchaseOrderType> complex type, and register and remove the <item> element mapping from the <ShippingNoticeType> complex type. In the SupplyChainEMF project, open SupplychainPackageImpl.java and add the code snippet Listing 11 shows.
Listing 11. Adding a local element mapping
private EClass purchaseItem;
public void initPurchaseItem()
{
purchaseItem = initEClass(createEClass(ITEM_TYPE),
ItemType.class, "item", !IS_ABSTRACT, !IS_INTERFACE);
}
public void removePurchaseItem()
{
if (purchaseItem != null)
this.eClassifiers.remove(purchaseItem);
}
private EClass shippingItem;
public void initShippingItem()
{
shippingItem = initEClass(createEClass(SHIPPING_ITEM_TYPE),
ShippingItemType.class, "item", !IS_ABSTRACT, !IS_INTERFACE);
}
public void removeShippingItem()
{
if (shippingItem != null)
this.eClassifiers.remove(shippingItem);
}
|
- Finally, implement the
submitPurchaseOrder(com.example.supplychain.www.PurchaseOrderType purchaseOrder) method in SupplyChainBindingImpl.java, as Listing 12 shows. It demonstrates how use the methods you created earlier.
Listing 12. Sample implementation of the submitPurchaseOrder method
public com.example.supplychain.www.PurchaseReferenceType
submitPurchaseOrder(com.example.supplychain.www.PurchaseOrderType purchaseOrder)
throws java.rmi.RemoteException
{
try
{
String customerReference = purchaseOrder.getCustomerReference();
/*
* Converting SOAPElement to PaymentMethodType. The local element
* mapping for paymentMethod is statically registered in the
* initializePackageContents() method of SupplychainPackageImpl.java
*/
PaymentMethodType paymentMethod =
(PaymentMethodType)domElement2EObject(soapElement2DOMElement((
SOAPElement)purchaseOrder.getPaymentMethod()));
/*
* Converting SOAPElement to ItemType. The local element mapping
* for item is dynamically registered and removed using the
* initPurchaseItem() and removePurchaseItem() methods.
*/
((SupplychainPackageImpl)SupplychainPackage.eINSTANCE).initPurchaseItem();
ItemType item = (ItemType)domElement2EObject(soapElement2DOMElement((
SOAPElement)purchaseOrder.getItem()));
((SupplychainPackageImpl)SupplychainPackage.eINSTANCE).removePurchaseItem();
ShippingNoticeType shippingNotice = purchaseOrder.getShippingNotice();
String recipient = shippingNotice.getRecipient();
String address = shippingNotice.getAddress();
/*
* Converting SOAPElement to ShippingItemType.
*/
((SupplychainPackageImpl)SupplychainPackage.eINSTANCE).initShippingItem();
ShippingItemType shippingItem =
(ShippingItemType)domElement2EObject(soapElement2DOMElement((
SOAPElement)shippingNotice.getItem()));
((SupplychainPackageImpl)SupplychainPackage.eINSTANCE).removeShippingItem();
float height = shippingItem.getHeight();
float length = shippingItem.getLength();
float width = shippingItem.getWidth();
float weight = shippingItem.getWeight();
boolean fragile = shippingItem.isFragile();
float total = 0;
total += item.getQuantity() * item.getPrice();
total += weight;
if (fragile)
total += 100;
ProcessingType processingType =
SupplychainFactory.eINSTANCE.createProcessingType();
StatusType status = SupplychainFactory.eINSTANCE.createStatusType();
status.setProcessing(processingType);
PurchaseReferenceType purchaseReference = new PurchaseReferenceType();
purchaseReference.setReferenceNumber(String.valueOf(Math.abs((
new Random()).nextInt())));
/*
* Converting StatusType to SOAPElement.
*/
purchaseReference.setStatus(domElement2SOAPElement(eObject2DOMElement(status)));
purchaseReference.setTotal(total);
return purchaseReference;
}
catch (Throwable t)
{
t.printStackTrace();
}
return null;
}
|
 |
Test the supply-chain Web service
You've completed the supply-chain Web service. Now use the Web Services Explorer to test it.
- Start the server where you deployed the supply-chain Web service. Open the server view. Click on menu Window >
Show View > Other.... Expand the Server folder and click Servers > OK.
- In the Servers view, right click on the WebSphere v5.1 Test Environment > Start.
- Right click on /SupplyChainWeb/WebContent/wsdl/com/example/supplychain/www/SupplyChainService.wsdl >
Web Services > Test with Web Services Explorer to launch the Web Services Explorer.
- In the operations table, click on the submitPurchaseOrder link.
- Enter the parameter values Table 1 shows.
| Table 1. Parameter values | |
Parameter
|
Value
| | customerReference | John Doe | | paymentMethod | tns:creditCard | | creditCardType | VISA | | creditCardNumber | 12345-67890 | | expiration | 2004-06-17 | | id | Plasma TV | | description | 42-inch | | quantity | 1 | | price | 3000 | | recipient | John Doe | | address | 123 Fake street | | height | 40 | | width | 25 | | length | 10 | | weight | 60 | | fragile | true |
- Click Go to invoke the submitPurchaseOrder operation. Figure 3 shows the result of the invocation.
Figure 3. Result of invoking the submitPurchaseOrder operation
 |
Conclusion
JAX-RPC defines a standard XML to Java type mapping model, however, this model has yet to provide standard mappings for the exhaustive list of XML data types. This article demonstrated how to unite the power of EMF and JAX-RPC to support XML data types that do not have standard mappings. Although EMF provides a solution, this approach requires users to work with two different programming models. In the future, the emerging technology Service Data Objects should provide a more elegant solution to this problem.
Get the products used in this article
If you are a developerWorks subscriber, you have a single user license to use WebSphere Studio Application and Site Developer, and other DB2®, Lotus®, Rational®, Tivoli®, and WebSphere® products -- including the Eclipse-based WebSphere Studio IDE -- to develop, test, evaluate, and demonstrate your applications. If you are not a subscriber, you can subscribe today.
Download | Name | Size | Download method |
|---|
| ws-mapprobcode.zip | | HTTP |
Resources
About the author  | |  | Jeffrey Liu is a software developer on the Rational Studio Web Services Tools Team at the IBM Toronto Lab.
You can reach Jeffrey at: jeffliu@ca.ibm.com. |
Rate this page
|  |