引言
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 轉換為 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 模式示例
<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 來開發、測試、評估和演示您的應用程序。
本文配套源碼