利用 Eclipse Modeling Framework 加强 JAX-RPC 类型映射的功能
2009-12-17 00:00:00 来源:WEB开发网引言
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 构建路径中。右键单击SupplyChainWebproject >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 转换为 DOMElementpublic 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 对象转换为 DOMElementpublic 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 转换为 SOAPElementpublic 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 模式示例<schema>
<element name="GlobalElement">
<complexType>
<sequence>
<element name="LocalElement" type="xsd:string"/>
</sequence>
</complexType>
</element>
</schema>
清单 8.全局元素实例<GlobalElement>
<LocalElement>Some String</LocalElement>
</GlobalElement>
清单 9.局部元素实例<LocalElement>Some String</LocalElement>
考虑 SupplyChainSchema.xsd 文档和 WSDL to Java Bean Skeleton wizard 生成的 JavaBean 时,您将看见有三个局部元素被映射到 SOAPElement:
来自 <PurchaseOrderType> 复杂类型的 <paymentMethod> 元素
来自 <PurchaseOrderType> 复杂类型的 <item> 元素
来自 <ShippingNoticeType> 复杂类型的 <item> 元素
要在 <paymentMethod> 这个局部元素和 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);
接下来,将为两个 <item> 局部元素建立自定义映射。和 <paymentMethod> 元素不同的是,不能在 initializePackageContents() 方法中添加静态映射条目,这是因为 EMF 模型对每个局部元素名称只允许一个映射。要克服这个缺点,可以象使用映射那样动态注册并移除必要的映射。清单 11 显示了 4 个方法,这 4 个方法允许您从 <PurchaseOrderType> 复杂类型中注册和移除 <item> 元素映射,以及从 <ShippingNoticeType> 复杂类型中注册和移除 <item> 元素映射。在 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 来开发、测试、评估和演示您的应用程序。
本文示例源代码或素材下载
赞助商链接