利用 Eclipse Modeling Framework 加强 JAX-RPC 类型映射的功能
内容:
级别: 中级
Jeffrey Liu (jeffliu@ca.ibm.com)
软件开发人员, IBM
2004 年 8 月
本文演示了如何使用 Eclipse Modeling Framework (EMF) 来加强 JAX-RPC 类型映射模型的功能。本文还提供了示例代码指导您从 Web 服务描述语言 (Web Services Description Language,WSDL) 文档创建 Web 服务的整个过程,Web 服务描述语言文档使用的是不支持 XML 数据类型的 JAX-RPC。
引言
JAX-RPC,也称为 JSR-101,是完成标准编程模型的一个重要步骤,该标准编程模型简化了 Java™ 平台上可互操作的 Web 服务的构建。由 XML 向 Java 类型映射模型的转换是 JAX-RPC 的关键,该转换是 Web 服务产品提供者的一个实现标准。没有这样的模型,Web 服务产品提供者会陷入定义专用类型映射的陷阱中,从而严重影响 Java 的互操作性问题。
虽然 JAX-RPC 在支持 XML 数据类型方面做了大量的工作,但是还有很多地方需要改进。而且,JAX-RPC 需要将任何不被支持的 XML 数据类型映射到 javax.xml.soap.SOAPElement 接口。javax.xml.soap.SOAPElement 接口没有为用户提供强类型的 Java 模型,也就是说用户必须编写自定义代码,然后通过 SOAPElement 实例来解析。这对初学者来说比较难,特别是当处理大的 XML 片段的时候。本文演示了如何使用 EMF 来支持没有标准 JAX-RPC 类型映射的 XML 数据类型。使用不支持 XML 数据类型的 JAX-RPC 生成 Web 服务并非易事,但是本文把 Web 服务工具和 IBM® WebSphere® Studio Application 以及 Site Developer V5.1.2 (Application and Site Developer) 中的 EMF 工具结合起来使用,提供了一个有效的解决方案。
创建供应链 Web 服务
要实现本文所介绍的方法,必须安装 WebSphere Application 和 Site Developer V5.1.2。如果需要的话,可以下载一个 60 天的试用版。
创建一个 Web 项目。单击菜单 File New Project... Web Dynamic Web Project Next,打开 New Dynamic Web Project wizard。
输入 SupplyChainWeb 作为 Web 项目的名称,选中 Configure advance options 复选框,然后单击 Next。
输入 SupplyChainEAR 作为 EAR 项目的名称,然后单击 Finish。
单击本文顶部的 Code 图标,下载 SupplyChainService.wsdl 和 SupplyChainSchema.xsd 到本地文件系统中。
将 SupplyChainService.wsdl 和 SupplyChainSchema.xsd 导入或复制到 SupplyChainWeb 项目的根目录下。
在 navigator 视图中,右键单击 SupplyChainService.wsdl Web Services Generate Java bean skeleton 打开图 1 所示的 WSDL to Java Bean Skeleton wizard。该向导生成一个基于 WSDL 文档中定义的信息的 Java 架构代码实现。接受所有的默认设置,然后单击 Finish。
图 1.WSDL to Java Bean Skeleton wizard
向导完成之后,您会在 tasks 视图中看见一些 WSDL 验证错误,这是由于 XML 模式文件 (SupplyChainSchema.xsd) 没有被复制到正确的地方。要更正这些错误,将 SupplyChainSchema.xsd 从 SupplyChainWeb 项目的根目录下复制到 /SupplyChainWeb/WebContent/WEB-INF/wsdl/ 和 /SupplyChainWeb/WebContent/wsdl/com/example/supplychain/www/ 这两个目录中。右键单击 SupplyChainService.wsdl Run validation,再次运行验证。
创建供应链 EMF 模型
WSDL to Java Bean Skeleton wizard 生成带一个或多个映射到 SOAPElement (具体的,PurchaseOrderType.java、PurchaseReferenceType.java 以及 ShippingNoticeType.java)属性的 JavaBean。在本部分中,将生成一个供应链 Web 服务的 EMF 模型来支持映射到 SOAPElement 的 XML 数据类型。
创建一个 EMF 项目。单击菜单 File New Project... Eclipse Modeling Framework EMF Project Next,打开 New EMF Project wizard。
输入 SupplyChainEMF 作为项目的名称,然后单击 Next。
选择 Load from an XML schema,然后单击 Next。
单击
Browse Workspace... 打开文件选择对话框。查找并选择 SupplyChainSchema.xsd,然后单击 OK。单击 Next。
选择 supplychain 包,然后单击 Finish。参阅图 2。
图 2.New EMF Project wizard
New EMF Project wizard 完成后,系统将打开 EMF Generator Editor。在这个编辑器中,右键单击 SupplyChainSchema 节点,选择 Generate Model Code。您已经成功生成了供应链 EMF 模型。在下一部分中,将学习如何将 EMF 代码集成到供应链 Web 服务中。
集成供应链 Web 服务与 EMF 模型
为 SupplyChainWeb 项目设置所有的依赖关系。将 SupplyChainEMF 项目添加到 SupplyChainEAR 作为一个实用的 JAR 文件,并指定从 SupplyChainWeb 项目到该实用 JAR 文件的 JAR 文件依赖性。
在应用程序部署描述编辑器 (Application Deployment Descriptor Editor) 中打开 /SupplyChainEAR/META-INF/application.xml。单击 Module 选项卡。
在 Project Utility JARs 栏中,单击 Add...,选择 SupplyChainEMF,然后单击 Finish。保存并关闭应用程序部署描述编辑器。
在 JAR Dependency Editor 中打开 /SupplyChainWeb/WebContent/META-INF/MANIFEST.MF。在 Dependencies 栏中选择 SupplyChainEMF.jar。保存并关闭应用程序部署描述编辑器。
将 EMF 库添加到 SupplyChainWeb 项目的 Java 构建路径中。右键单击 SupplyChainWeb project Properties Java Build Path。单击 Libraries 选项卡,选择 Add Variable...。
选择EMF_COMMON、EMF_ECORE 以及 EMF_ECORE_XMI。单击 OK OK。
清单 1 显示了用到的所有导入语句。在 Java 编辑器中打开 /SupplyChainWeb/JavaSource/com/example/supplychain/www/SupplyChainBindingImpl.java 并添加这些导入语句。
清单 1.导入语句
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;
使用生成的供应链 EMF 模型之前必须先初始化。初始化过程在 XML 模式 (SupplyChainSchema.xsd) 中声明的元素和 EMF 代码生成器创建的 Java 类之间建立了一个映射。该映射用于 XML 片段与相应的基于 EMF 的 Java 类之间的相互转换。要初始化供应链 EMF 模型,将下面的静态代码块添加到 SupplyChainBindingImpl.java 中。
清单 2.初始化 EMF 包
static
{
SupplychainPackageImpl.init();
}
接下来,在 SupplyChainBindingImpl.java 中添加 4 个方法,这些方法将 SOAPElement 转换为 DOMElement,然后再转换为相应的基于 EMF 的 Java 类,也可以反过来转换。清单 3、4、5 以及 6 显示了这些方法。soapElement2DOMElement(SOAPElement soapElement) 方法和 domElement2SOAPElement(Element e) 方法利用两个特定于应用程序和站点开发者实现的方法:getAsDOM() 和 setAlternateContent(e),来负责 SOAPElement 到 DOMElement 的转换。要从特定于提供商的代码中清除这些方法,可以手动的遍历 SOAPElement 并构造相应的 DOMElement。
在本文中,可以使用现成的方法,也就是说,可以使用应用程序和站点开发者实现提供的方法。事实上,如果 SOAP 附带了 Java V1.2 (SAAJ)- 兼容实现的附加 API 函数,那么就不再需要将 SOAPElement 转换为 DOMElement,这是因为 SAAJ V1.2 需要 SOAPElement 以直接扩展 DOMElement。
清单 3.将 SOAPElement 转换为 DOMElement
public Element soapElement2DOMElement(SOAPElement soapElement)
throws Exception
{
return ((com.ibm.ws.webservices.engine.xmlsoap.SOAPElement)soapElement).getAsDOM();
}
清单 4.将 DOMElement 转换为 EMF 对象
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;
}
清单 5.将 EMF 对象转换为 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();
}
清单 6.将 DOMElement 转换为 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;
}
全局元素和局部元素
正如前面所提到的,供应链 EMF 模型依靠映射到 Java 的元素将 XML 片段转换为相应的基于 EMF 的 Java 类。但是,默认的情况是,EMF 代码生成器只为全局元素生成映射条目,而不为局部元素生成。全局元素是 XML 模式文档中作为模式元素的子元素来声明的元素,而局部元素却不是。默认的映射清单不包括局部元素,因此,供应链 EMF 模型不能转换描述局部元素实例的 XML 片段。研究一下清单 7 中的示例 XML 模式。相应的 EMF 模型识别清单 8 中的全局元素实例。相反,清单 9 中的局部元素实例却导致异常。要支持局部元素的转换,必须在 Java 映射中添加自定义元素。
清单 7.XML 模式示例
清单 8.全局元素实例
Some String
清单 9.局部元素实例
Some String
考虑 SupplyChainSchema.xsd 文档和 WSDL to Java Bean Skeleton wizard 生成的 JavaBean 时,您将看见有三个局部元素被映射到 SOAPElement:
来自 复杂类型的 元素
来自 复杂类型的 元素
来自 复杂类型的 元素 要在 这个局部元素和 com.example.supplychain.PaymentMethodType 这个 Java 类之间建立自定义映射,请在 SupplyChainEMF 项目中打开 /SupplyChainEMF/src/com/example/supplychain/impl/SupplychainPackageImpl.java。将清单 10 中的代码片段添加到 initializePackageContents() 方法的尾部。该方法将作为初始化的一部分被调用。
清单 10.添加一个局部元素映射
initEClass(paymentMethodTypeEClass, PaymentMethodType.class,
"paymentMethod", !IS_ABSTRACT, !IS_INTERFACE);
接下来,将为两个 局部元素建立自定义映射。和 元素不同的是,不能在 initializePackageContents() 方法中添加静态映射条目,这是因为 EMF 模型对每个局部元素名称只允许一个映射。要克服这个缺点,可以象使用映射那样动态注册并移除必要的映射。清单 11 显示了 4 个方法,这 4 个方法允许您从 复杂类型中注册和移除 元素映射,以及从 复杂类型中注册和移除 元素映射。在 SupplyChainEMF 项目中,打开 SupplychainPackageImpl.java 并添加清单 11 所示的代码片段。
清单 11.添加一个局部元素映射
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);
}
最后,如清单 12 所示,执行 SupplyChainBindingImpl.java 中的 submitPurchaseOrder(com.example.supplychain.www.PurchaseOrderType purchaseOrder) 方法。该清单演示了如何使用前面创建的方法。
清单 12.执行 submitPurchaseOrder 方法示例
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;
}
测试供应链 Web 服务
您已经完成了供应链 Web 服务。现在使用 Web Services Explorer 对其进行测试。
启动部署了供应链 Web 服务的服务器。打开 server 视图。单击菜单 Window Show View Other...。展开 Server 文件夹,然后单击 Servers OK。
在 Servers 视图中,右键单击 WebSphere v5.1 Test Environment Start。
右键单击 /SupplyChainWeb/WebContent/wsdl/com/example/supplychain/www/SupplyChainService.wsdl Web Services Test with Web Services Explorer 启动 Web Services Explorer。
在操作栏中,单击 submitPurchaseOrder 链接。
输入如表 1 所示的参数值。
表 1.参数值
参数
值
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
单击 Go 调用 submitPurchaseOrder 操作。图 3 显示了调用结果。
图 3.调用 submitPurchaseOrder 操作的结果
结束语
JAX-RPC 定义了一个 XML 到 Java 类型映射的标准模型,但是,该模型还需要为所有的 XML 数据类型提供标准映射。本文演示了如何联合 EMF 和 JAX-RPC 的功能来支持没有标准映射的 XML 数据类型。虽然 EMF 提供了一个解决方案,但是该方法需要用户同时使用两种不同的编程模型。以后,新兴技术服务数据对象 (Service Data Objects) 将会针对该问题提供更好的解决方案。
获取本文中所使用的产品和工具
如果您是一个 developerWorks 订阅者,那么您将具有一个单用户许可证,可以使用 WebSphere Studio Application and Site Developer 和其他的 DB2®、Lotus®、Rational®、Tivoli®,以及 WebSphere® 产品 —— 其中包括基于 Eclipse 的 WebSphere Studio IDE 来开发、测试、评估和演示您的应用程序。如果您不是一个订阅者,您可以现在订阅。
参考资料
查看下列规范:
Java APIs for XML Messaging: SOAP with Attachments API for Java (JSR-67)
Service Data Objects (JSR-235)
阅读 XML Schema Part 0: Primer 关于 XML 模式工具的描述 (W3C,2001 年 5 月 2 日)。
通过阅读 XML Schema Part 1: Structures(W3C,2001 年 5 月 2 日)更好的理解 XML 模式定义语言的结构。
阅读 XML Schema Part 2: Datatypes,讨论 XML 模式(W3C,2001 年 5 月 2 日)中的数据类型。
下载 60 天试用版的 IBM WebSphere Studio Application and Site Developer V5.1.2。
在 Eclipse.org 上下载 Eclipse 项目、关于 Eclipse 工具的文章、新闻组及更多相关内容。
在 Developer Bookstore 购买关于各种的技术主题的打折图书。
下载源代码
关于作者
Jeffrey Liu 是 IBM 多伦多实验室 Rational Studio Web 服务工具小组的一位软件开发人员,您可以通过 jeffliu@ca.ibm.com 与 Jeffrey 联系。