注意:您應該熟悉 WebSphere Studio Application Developer Integration Edition Version 5.1.1 Web 服務開發環境、ASP .NET Web 服務,並了解構建 BPEL 流程的知識。本文還帶有 BPEL 業務流程的樣本代碼。
引言
由於 XML 和 Web 服務需要使用 BPEL,它迅速成為面向服務體系結構(SOA)的基礎,BPEL 還提供了 WebSphere Application Server Enterprise Process Choreographer(Process Choreographer)的公開標准。這些年來,許多企業應用程序都分別在 J2EE 和.NET 平台上被獨立和並行的開發和部署。這些業務應用程序都設計有細粒度的業務功能。例如,在 J2EE 中,使用實體 bean 實現信息持久性,並使用會話 bean 實現業務邏輯。這些業務應用程序同樣還在有限的業務域裡提供集成框架,用於後端或是遺留企業應用程序的集成。舉例來說,Java Connector Architecture(JCA)和 Java Messaging Service(JMS)都是典型的 J2EE 應用程序集成框架。
隨著 Web 服務的出現,後端企業應用程序通過使用 WSDL 被公開為可發現並可調用的業務服務。WSDL 為 Web 服務接口定義了服務語義,例如操作、協議綁定和消息類型等。BPEL 層在 WSDL 之上,它指定參與流程流的復合 Web 服務的行為。因此,它使業務分析人員和架構師可以定義業務流程流的邏輯,並可以使用 BPEL 來支持與 J2EE Web 服務和 .NET Web 服務的長期運行的會話。
實際上,BPEL 流程流成功與否,基本取決於每個 Web 服務的 WSDL 文檔中定義的 XML 服務語義。XML schema 使 XML 與其他文件格式區別開來,XSD 是 XML schema 定義,它是綜合且復雜的數據類型定義系統。簡單地說,XSD 定義了 XML 文檔的外型。使用 XSD 設計簡單且強類型(strongly-typed)的對象是 Web 服務互操作性的基礎。“改進 J2EE 技術和 .NET 間的互操作性”(本系列的第 1 部分)指出,許多 Web 服務編程人員忽視了 XSD schema 設計的重要性。換句話說,即他們使用自己喜愛的編程語言來為 Web 服務實現編寫代碼,並隨後用供應商的工具從實現中獲得 Web 服務語義。這種自底向上的方法產生了有關互操作性的問題。
.NET 和 J2EE 之間的互操作性問題通常源於 XML 命名空間和復雜數據類型,例如嵌套的復雜類型數組以及日期和時間(本系列的第 2 部分和第 3 部分)。本文中講述的技巧將展示在 BPEL 流程集成中如何在兩個平台之間安全並正確的傳遞嵌套數組、復雜類型和日期。但這需要您為這些復雜類型仔細設計 XSD schema。
著手准備
要構建流程,您需要在 Windows 中安裝 IBM WebSphere Studio Application Developer V5.1.1 和 Microsoft .NET Framwork 1.1。對於本文來說,這兩種產品都在同一台機器上安裝並運行。.NET Visual Studio 是用來構建 .NET Web 服務的集成工具,在本技巧中不予使用。
IIS 的文檔根目錄在缺省情況下為 C:\Inetpub\wwwroot。我將使用該目錄發布 .NET Web 服務。同時,.NET Framework 1.1 安裝在 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 目錄下,且 SDK 在 C:\Program Files\Microsoft.NET\SDK\v1.1 目錄下。
將以下目錄添至系統 PATH 變量:
c:\Windows\Microsoft.NET\Framework\v1.1.4322
c:\Program Files\Microsoft.NET\SDK\v1.1\Bin
BPEL 業務流程的樣本代碼在下載部分。
典型的互操作業務場景
設想一個購買場景,購買者通過貨物代理商來執行定購請求。貨物代理商擁有許多提供貨源的供應商;每個參與者都是獨立的訂戶且擁有自己的產品庫存管理系統。也就是說,某個供應商可能運行 J2EE Web 服務來管理其庫存而其它供應商可能會使用 .NET Web 服務來進行相同的操作。
購買流程從向不同的供應商索取產品報價開始。在購買者提交訂單之前,代理商與每個供應商聯系以獲取相應產品的報價,且每個供應商將返回該產品的詳細信息。之後購買者將浏覽信息並繼續進行下個定購流程。圖 1 展示了兩個供應商(Supplier A 和 Supplier B)報價請求的流程圖。購買者請求代理商提供報價,代理商將該報價請求傳給各供應商,並隨後將供應商提供的信息返回給購買者。
圖 1.報價請求的流程圖
在該圖中:
Buyer 是提出購買請求的客戶端。
Agent 是業務流程,請求供應商提供產品信息並處理購買者的訂單。
Supplier A 是 Java Web 服務,管理供應商 A 的庫存。
Supplier B 是 .NET Web 服務,管理供應商 B 的庫存。
對於購買者提出的購買請求,代理流程將首先對各供應商構造產品報價請求。各供應商作出反應,提供相應的產品信息,包括價格、數量和其它產品信息。代理隨後將這些產品信息返回給購買者以供浏覽和定購。
在接下來的章節中,我們將構建代理流程,為 Supplier A 構建 Java Web 服務,並為 Supplier B 構建 .NET Web 服務。
創建 Supplier A 的 Java Web 服務
在本系列的第 1 部分中,您將發現,常被忽視的最重要的最佳實踐之一是用於 Web 服務的 XML schemas 和 WSDL 設計。編程人員通常用自己喜愛的編程語言開始構建 Web 服務,然後使用供應商工具獲取 Web 服務語義並公開 WSDL。這就是所謂的自底向上的方法——它並不考慮 Web 服務是圍繞消息進行的,且數據和消息類型都必須設計得很謹慎而不是簡單地用工具生成。本部分將展示如何使用 WebSphere Studio Application Developer Integration Edition Version 5.1.1 為 Web 服務設計 XML schema 和 WSDL。
對於 Supplier A,庫存服務必須執行至少兩個操作:getQuote 用來返回庫存中的產品信息,fulfillOrder 用來執行來自代理的定購請求。下文中的圖 2 展示了該服務接口的流程。
圖 2. Supplier A Web 服務接口圖
Product 是一個 complexType,有四個屬性;每個屬性都是 基本數據類型。本部分將展示如何在 WebSphere Studio Application Developer Integration Edition Version 5.1.1 中利用 XML schema 編輯器來用 XML schema 語言定義產品類型。下個部分將展示如何設計 .NET Web 服務的復合 Product complexType 來演示互操作性。
步驟 1.創建服務項目
首先,創建服務項目和包,用來為所有伙伴 Web 服務和流程定義保存 XML schema 文件和 WSDL。
啟動新的 WebSphere Studio Application Developer Integration Edition Version 5.1.1 工作區。
選擇 File>New>Service Project。
指定服務項目名稱為 QuoteProcessService,並點擊 Finish。
右鍵單擊 QuoteProcessService 項目並選擇 New>Package。
輸入新 Java 包的名稱 quote.process 並單擊 Finish。
步驟 2.創建新的 XML
接下來,創建新的 XML 並定義 Product complexType。
右鍵單擊 quote.process 包並選擇 New>XML schema。
輸入新的 schema 名稱 SupplierASchema.xsd 並單擊 Finish。打開 XML schema 編輯器。
在 Outline 視圖中,選擇 SupplierASchema.xsd。
在 Schema 面板中,輸入 http://schema.a.supplier 作為命名空間 URI。這將作為 schema 的目標命名空間。點擊 Apply。
在 Outline 視圖中,右鍵單擊 SupplierASchema.xsd 並單擊 Add Complex Type。
在 Outline 視圖中,選擇 NewComplexType。
在 Complex Type 面板中,輸入復雜類型的名稱:Product。
在 Outline 視圖中,右鍵單擊 Product 類型並單擊 Add Content Model。
在 Outline 視圖中,擴展 Product。
右鍵單擊 content model icon 並單擊 Add Element。添加 NewElement。
在 Outline 視圖中,選擇 NewElement.
在 Element 面板中,將 NewElement 重新命名為 _name 並將類型設置為 xsd:string。
請注意步驟 4,schema 的目標命名空間設置為 http://schema.a.supplier 而不是 http://a.supplier/schema。這是 JAX-RPC 行為的結果:JAX-RPC 將使用 URI 的域名部分來生成可序列化產品類的包名。為避免潛在的命名沖突,命名空間 URI 的域名部分應該盡可能的細粒度,如"改進 J2EE 技術和 .NET 間的互操作性,第 3 部分"所示。另外一個原因是如果序列化器類的包名是 supplier.a ,那麼當接收到命名空間限定的產品對象({http://a.supplier/schema}Product)時,一些客戶端查詢反序列化器會失敗。
重復最後三個步驟,創建 Product 類型的其它元素並保存 XSD 文件。
_price: xsd:float
_qty: xsd:int
_refurbished: xsd:boolean
步驟 3. 創建 Supplier A 的 WSDL
您可以為 Supplier A 設計 WSDL 並定義服務方法(操作)和消息綁定。在報價流程中,僅需 getQuote 操作。
首先,創建空 WSDL,並導入在之前步驟中定義的產品 schema。
右鍵單擊 quote.process 包。選擇 New>Other>Web Services>WSDL 並單擊 Next。
輸入新的 WDSL 名 SupplierAService.wsdl 並單擊 Next。
在向導中,設置目標命名空間為 http://a.supplier/service/ 並單擊 Finish。打開 WSDL 編輯器。
在 Outline 視圖中,右鍵單擊 Imports 並選擇 Add Child>Import。
在 Import 面板中,單擊在位置 box 旁邊的 push button。
浏覽到 SupplierASchema.xsd 並單擊 OK。
Product complexType 被導入到命名空間 http://schema.a.supplier 下的 WSDL 中。
步驟 4. 創建消息和消息部件
接下來,創建消息和消息部件。對於每次 getQuote 調用,將會產生請求消息傳送產品名稱,以及響應消息返回來自庫存的 Product 對象。
在 Outline 視圖中,右鍵單擊 Messages 並選擇 Add Child>Message。
輸入名稱 getQuoteRequest。
右鍵單擊新創建的 getQuoteRequest 並選擇 Add Child>Part。
將新消息部件命名為 productName 並單擊 OK。
請注意,消息部件名稱的缺省類型為 xsd:string。接受該缺省類型。現在重復以上步驟。用消息部件 product 創建新的消息 getQuoteResponse,該消息是導入的 SupplierASchema.xsd 中已定義的 Product 類型。
右鍵單擊 Product 消息部件並單擊 Set Type。
選中 Select Existing Type 單選框,並選擇 xsd1:Product,如以下圖 3 所示。單擊 Finish。
圖 3. 指定 product 消息部件類型
步驟 5. 定義 Port Type
接下來,定義 Port Type,所有的服務操作都在此處被展示。在該場景中,只定義了 getQuote 操作。
在 Outline 視圖中,右鍵單擊 Port Types 並選擇 Add Child>Port Type。
將新端口類型命名為 SupplierAQuotePortType 並單擊 OK。
右鍵單擊 SupplierAQuotePortType 並選擇 Add Child>Operation。
輸入新操作名 getQuote 並單擊 OK。
右鍵單擊 getQuote 操作並選擇 Add Child>input。
右鍵單擊 input>Set Message。
選擇現有的 tns:getQuoteRequest 消息,該消息在步驟 4. 創建消息和消息部件中已定義,然後點擊 Finish。
重復以上的三步,創建 getQuote 操作的輸出並將其鏈接至 tns:getQuoteResponse。
步驟 6. 定義綁定協議
最後,您需要定義服務端口的綁定協議。
在 Outline 視圖中,右鍵單擊 Bindings 並選擇 Add Child>Binding。
指定綁定細節,如以下圖 4 所示。請確保選擇 document/literal 作為 SOAP 綁定選項。單擊 Finish。
右鍵單擊 Services 並選擇 Add Child>Service。
輸入 SupplierAQuoteService 作為新服務名稱並單擊 OK。
右鍵單擊 SupplierAQuoteService 並選擇 Add Child>Port。
如圖 5 所示,輸入端口細節並單擊 Finish。
圖 4.指定綁定細節
圖 5.指定服務端口細節
步驟 7. 實現 Web 服務
最後,您可以實現基於 SupplierAService.wsdl 和 SupplierASchema.xsd 的 Web 服務。WebSphere Studio 可以從 WSDL 生成 Skeleton Java bean Web 服務。
選擇 File>New>Other>Web Services>Web service 並單擊 Next。
在接下來的 Web Service 向導中,選擇 Web 服務類型為 Skeleton Java bean Web Service。對其它域的保留缺省設置並單擊 Next。
在接下來的窗口中,指定 SupplierAServiceEAR 為 Service project EAR,並指定 SupplierAServiceWeb 為 Service Web 項目。這是擁有 Java Web 服務的企業項目。單擊 Next。
浏覽到創建好的 SupplierAService.wsdl。單擊 OK,單擊 Next,然後單擊 Finish。
檢查 SupplierAServiceWeb 項目。可序列化的 Product 類由 SupplierASchema.xsd 中定義的復雜類型 Product 生成,且 Java Skeleton Web 服務也已構建。但是,這還只是有接口功能的空服務;接口操作 getQuote 的具體實現需要在 SupplierAQuoteServiceBindingImpl 中手工提供。
首先,添加構造函數至 Product 類。
清單 1.添加構造函數至 Product 類
public Product(String name, int qty, float price, boolean isRefurbished) {
this.set_name(name);
this.set_qty(qty);
this.set_price(price);
this.set_refurbished(isRefurbished);
}
接下來,添加構造函數至 SupplierAQuoteServiceBindingImpl 類,用以對庫存硬編碼。實際上,您可能需要公開接口方法,例如 addInventory(Product item) 用來重新儲存產品。
清單 2.添加構造函數至 SupplierAQuoteServiceBindingImpl 類
private static Hashtable fCurrentInventory = new Hashtable();
public SupplierAQuoteServiceBindingImpl() {
fCurrentInventory.put(
"IBM ThinkPad T40",
new Product("IBM ThinkPad T40", 200, 1499.99f, false));
fCurrentInventory.put(
"Dell Inspiron 4000",
new Product("Dell Inspiron 4000", 100, 999.99f, true));
fCurrentInventory.put(
"Toshiba Satellite 2210X",
new Product("Toshiba Satellite 2210X", 300, 599.99f, false));
}
用清單 3 中的代碼替代 SupplierAQuoteServiceBindingImpl 類中的 getQuote 方法。
清單 3. 替代 getQuote 方法
public Product getQuote(java.lang.String productName)
throws java.rmi.RemoteException {
if (fCurrentInventory.containsKey(productName))
return (Product) fCurrentInventory.get(productName);
else
return new Product(productName, 0, 0f, false);
}
Supplier A 的報價 Web 服務已經准備好,可以進行部署和運行了。在 BPEL 流程創建之前還不能對其進行測試。
創建 Supplier B 的 .NET Web 服務
對於 Supplier B 的 .NET Web 服務,schema 要更復雜一些:在其它復雜類型中再嵌套復雜類型數組。在編程語言中添加的這種復雜性並不十分奇怪,事實上是非常正常的事情。但是,它經常是導致 XML 消息序列化失敗的起因。特別是,消息接受方通常不能匹配合適的 XML 序列化器類。:dateTime 也是 J2EE 和 .NET 間出現互操作性問題的常見根源。本技巧的主要目的之一,就是說明如何為消息和數據類型謹慎地設計 XML schema,以避免出現互操作性問題。
要構建 .NET Web 服務,您需要使用稍微有些不同的方法。但通常都應該首先設計 XSD schema。
假設 Supplier B 對保存它的庫存產品信息有不同的需求。與 Supplier A 中列出庫存產品名稱不同,Supplier B 保存了產品信息清單,例如生產日期、庫存日期或是重新進貨日期等。
圖 6.Supplier B Web 服務的接口圖
在 UML 圖中,Product 有 _dates 屬性,該屬性是 DateInfo
的集合,DateInfo 都是復雜類型。在編程語言中,UML 集合轉化成為數組。但在另一個端,如何用 XML schema 或是 XSD 類型來表示 Product 和 DateInfo 之間的關系呢?此時,需要另一個復雜類型的屬性 ArrayOfDateInfo 用來描述兩者之間的關系。ArrayOfDateInfo 類型有 DateInfo 類型元素的未綁定序列。因此,要正確序列化 .NET Web 服務中的 Product 對象,需要在 XML Schema 中定義三種復雜類型:DateInfo、ArrayOfDateInfo 和 Product。
和先前演示的步驟類似,首先用目標命名空間 http://schema.b.supplier 創建 SupplierBSchema.xsd 並定義復雜類型 DateInfo,如清單 4 所示。
清單 4. 定義復雜類型 DateInfo
<complexType name="DateInfo">
<sequence>
<element name="_date" type="dateTime"/>
<element name="_desc" type="string"/>
</sequence>
</complexType>
接下來,通過 SupplierBSchema.xsd 中 DateInfo 類型元素的未綁定序列來添加復雜類型 ArrayOfDateInfo。
在 Outline 視圖中,右鍵單擊 SupplierBSchema.xsd 並單擊 Add Complex Type。
將新的復雜類型重命名為 ArrayOfDateInfo。
右鍵單擊 ArrayOfDateInfo 並單擊 Add Content Model。
右鍵單擊 content model icon 並單擊 Add element。
將新元素重新命名為: DateInfo。
設置用戶定義的復雜類型:SupplierBSchema:DateInfo。
將 minOccurs 屬性設置為零,並將 maxOccurs 屬性設置為 unbounded。
ArrayOfDateInfo schema 的結果如 清單 5 所示。
清單 5. ArrayOfDateInfo schema
<complexType name="ArrayOfDateInfo">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="_dateInfo" type="SupplierBSchema:DateInfo"/>
</sequence>
</complexType>
按照如定義 complexType Product 類似的步驟進行,如以下清單 6 所示。
清單 6. complexType Product
<complexType name="Product">
<sequence>
<element name="_name" type="string"/>
<element name="_qty" type="int"/>
<element name="_price" type="float"/>
<element name="_dates" type="SupplierBSchema:ArrayOfDateInfo"/>
</sequence>
</complexType>
在 .NET Framework 1.1 中,XML Schema Definition 工具(Xsd.exe)可通過 XSD 文件生成運行時類。Xsd.exe 實用程序通過 XSD schema 中生成一組 C# 類模板。通過模板,您可以提供具體的實現並生成整個項目。然而,Xsd.exe 實用程序需要在 schema 中定義至少一個頂級元素,因此您需要定義一個全局元素:productItem。
在 Outline 視圖中,右鍵單擊 SupplierBSchema.xsd 並單擊 Add Global Element。
將該元素命名為 productItem,並將其類型設置為 SupplierBSchema:Product。
保存文件。
現在,導出 SupplierBSchema.xsd 文件到一個目錄,並運行該目錄中的 Xsd.exe 命令,生成 C# 類型類:xsd.exe SupplierBSchema.xsd /classes。
一組 C# 類型的類在 SupplierBSchema.cs 文件中生成。DateInfo 和 Product 類通過 http://schema.b.supplier 命名空間定義並限定。請參見 Download 部分關於類的完整資料。在設計完互操作性場景中最重要的部分後,接下來開始構建 .NET Web 服務實現。在 構建代理流程 部分中,SupplierASchema.xsd 和 SupplierBSchema.xsd 同樣是構建 BPEL 流程的起始點。
要構建 Supplier B 的 .NET Web 服務,您可以使用 .NET Visual Studio 構建 .NET 部件,或者在 .asmx 文件中,簡單地編寫 C# 代碼並將類型類封裝在 SupplierBSchema.cs 文件中,此處將使用後一種方法。
復制 SupplierBSchema.cs 文件至C:\Inetpub\wwwroot\SupplierB\目錄並重命名為 SupplierBQuoteService.asmx,在編輯器中打開。
注釋掉以下行: [System.Xml.Serialization.XmlRootAttribute("productItem", Namespace="http://schema.b.supplier", IsNullable=false)] .
將構造函數添加至 Product 和 DateInfo 類中,並進行初始化。在 .NET 中,序列化類也需要缺省構造函數。public DateInfo() {}
public DateInfo(DateTime date, string desc) {
_date = date;
_desc = desc;
}
andpublic Product() {}
public Product(string name, int qty, float price, DateInfo[] dates) {
_name = name;
_qty = qty;
_price = price;
_dates = dates;
}
添加 Web 服務類 SupplierBQuoteService 並在命名空間 http://b.supplier/service 下將 getQuote 方法公開為 document/literal Web 服務方法。在 .NET 中,document/literal 是缺省綁定樣式:[WebService(Namespace="http://b.supplier/service")]
public class SupplierBQuoteService {
private static Hashtable fCurrentInventory = null;
private static Hashtable getCurrentInventory()
{
return fCurrentInventory;
}
public SupplierBQuoteService()
{
fCurrentInventory = new Hashtable();
Product item1 = new Product("IBM ThinkPad T40", 200, 1399.99f,
new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"),
new DateInfo(DateTime.Now.AddYears(3), "Expiry Date")});
Product item2 = new Product("Dell Inspiron 4000", 200, 899.99f,
new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"),
new DateInfo(DateTime.Now.AddYears(5), "Expiry Date")});
Product item3 = new Product("Toshiba Satellite 2210X", 200, 599.99f,
new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"),
new DateInfo(DateTime.Now.AddYears(10), "Expiry Date")});
getCurrentInventory().Add("IBM ThinkPad T40", item1);
getCurrentInventory().Add("Dell Inspiron 4000", item2);
getCurrentInventory().Add("Toshiba Satellite 2210X", item3);
}
[WebMethod]
public Product getQuote(string quoteItemName) {
string item = quoteItemName;
if (!getCurrentInventory().ContainsKey(item))
return new Product(item, 0, 0,
new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"),
new DateInfo(DateTime.Now, "Expiry Date")});
else
return (Product)(getCurrentInventory()[item]);
}
}
完成的 SupplierBQuoteService.asmx 文件包含在 Download 部分中。
現在您可以在浏覽器上測試 Supplier B Web 服務的 getQuote 方法。
在浏覽器上,輸入以下 URL:http://localhost/SupplierB/getQuoteServiceImpl.asmx。
只返回 Web 服務方法 getQuote。單擊 getQuote 方法。
在文本框中輸入 IBM ThinkPad T40 並單擊 Invoke。
將返回 IBM ThinkPad T40 產品信息,如以下 圖 7 所示。
圖 7. .NET Web 服務的 getQuote 方法的測試結果
請注意 Manufacture Date 和 Expiry Date 是如何顯示的。
在浏覽器中輸入 URL: http://localhost/SupplierB/SupplierBQuoteService.asmx?wsdl。浏覽器將顯示 .NET Web 服務的 WSDL 文檔。雖然該文檔由 .NET WSDL 引擎生成,但日期類型 schema 和命名空間直接來自於前面步驟中設計的 SupplierBSchema.xsd。在服務項目中,將 WSDL 導入至 quote.process 包。
構建代理流程
在本部分中,您將定義代理 Quote Process 接口。該流程的輸出數據類型由 Supplier A 和 Supplier B 的產品報價結果組合而成,如圖 8 中的 UML 所示。
圖 8.Quote Process 的接口圖
您需要定義復雜類型,通過輸入兩個分別來自 SupplierASchema.xsd 和 SupplierBSchema.xsd 的 Product 復雜類型來組成最後的報價信息。
步驟 1.定義復雜類型
首先,創建 QuoteProcess.xsd schema 文件並輸入 SupplierASchema.xsd 和 SupplierBSchema.xsd:
右鍵單擊 quote.process 包並選擇 New>XML Schema。
輸入新 schema 名稱: QuoteProcessSchema.xsd 並單擊 Finish。打開模式編輯器。
在 Outline 視圖中,選擇 QuoteProcessSchema.xsd。
在 Outline 視圖中,右鍵單擊 QuoteProcessSchema.xsd 並單擊 Add Import。
在 Outline 視圖中,擴展 + sign 並單擊 import icon。
在 Import 面板中,浏覽到 SupplierASchema.xsd 文件並單擊 Finish。
重復步驟 4 至 6,並導入 SupplierBSchema.xsd 文件。
步驟 2.創建復雜類型
接下來,創建 ProductQuotes 復雜類型:
在 Outline 視圖中,右鍵單擊 QuoteProcess.xsd 並單擊 Add Complex Type。
在 Complex Type 窗口中,將新的復雜類型命名為 ProductQuotes。
在 Outline 視圖中,右鍵單擊 ProductQuotes 並單擊 Add Content Model.
在 Outline 視圖中,右鍵單擊 icon: 並單擊 Add Element。
在 Element 窗口,將新元素命名為 SupplierAQuote。
將 SupplierAQuote 元素設置為用戶定義的復雜類型 SupplierASchema:Product。
重復最後三個步驟,添加另一元素 SupplierBQuote,並將其設置為用戶定義的復雜類型 SupplierBSchema:Product。
在 Source 視圖中,作為結果產生的 ProductQuotes schema 如清單 7 所示。
清單 7.ProductQuotes schema
<?xml version="1.0"?>
<schema targetNamespace="http://www.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:QuoteProcessSchema="http://www.ibm.com"
xmlns:SupplierASchema="http://schema.a.supplier" xmlns:SupplierBSchema=
"http://schema.b.supplier">
<import namespace="http://schema.b.supplier" schemaLocation=
"SupplierBSchema.xsd"/><import schemaLocation=
"SupplierASchema.xsd" namespace="http://schema.a.supplier"/>
<complexType name="ProductQuotes">
<sequence>
<element name="SupplierBQuote" type="SupplierBSchema:Product"/>
<element name="SupplierAQuote" type="SupplierASchema:Product"/>
</sequence>
</complexType>
</schema>
BPEL 流程將作為 Web 服務進行部署和運行。接下來的步驟將創建流程接口 WSDL 並導入 QuoteProcessSchema.xsd。
步驟 3.創建流程接口 WSDL
創建流程接口 WSDL。
在 quote.process 包中,創建空 WSDL 且命名為 quoteProcessClient.wsdl。在向導中,將目標命名空間設置為 http://quote.process/quoteProcessClient/。
在 Outline 視圖中打開 WSDL 編輯器後,右鍵單擊 Imports 並輸入 QuoteProcessSchema.xsd 文件。
步驟 4.定義流程入站和出站消息
接下來,定義流程入站和出站消息。
在 Outline 視圖中,右鍵單擊 Messages 並選擇 Add Child>Message。
將新消息名設置為 getQuotesRequest 並單擊 OK。
在 Outline 視圖中,右鍵單擊 getQuotesRequest 並選擇 Add Child>Part。
輸入部件名 productName。其缺省類型為 xsd:string。
重復上述步驟,創建新消息 getQuotesResponse,添加消息部件 quotes,並為 quotes 設置類型。
在 Outline 視圖中,右鍵單擊 quotes 並單擊 Set Type。
將類型指定為 xsd1:ProductQuotes 並單擊 Finish,如以下圖 9 所示。
圖 9.指定 quotes 類型
步驟 5. 添加端口類型
接下來,為流程添加 Port Types 以及操作並定義輸入和輸出。
在 Outline 視圖中,右鍵單擊 Port Types 並選擇 Add Child>Port Type。
添加新 Port Type,名為 QuotesProcessPortType。
右鍵單擊 QuotesProcessPortType 並選擇 Add Child>Operation。
將操作命名為 getQuotes。
右鍵單擊 getQuotes 操作,選擇 Add Child>input 並右鍵單擊 input。
單擊 Set Message 並選擇 Select an existing message 單選框。
將輸入消息設置為 tns:getQuotesRequest 並單擊 Finish。
重復最後三個步驟,添加輸出,設置消息為 tns:getQuotesResponse,並保存 quoteProcessClient.wsdl。
步驟 6. 構建業務流程流
現在,構建業務流程流。首先,創建空流程定義文件。
右鍵單擊 QuoteProcessService 中的 quote.process 包,並選擇 New>Business Process。
將業務流程命名為 QuotesProcess 並單擊 Next。
選擇 Sequence-based BPEL Process 並單擊 Finish。
步驟 7. 創建伙伴鏈接
BPEL 流程通過名為 <partnerLink> 的簡單概念調用 Web 服務。<partnerLink> 是 Web 服務 WSDL 文件定義的操作的 BPEL 抽象。伙伴鏈接讀取外部 Web 服務的 portType,該外部 Web 服務由 WSDL 定義。並允許 portType 中的實際操作和調用活動相關聯。每個伙伴鏈接都與邏輯工作角色相關聯。
在該流程中,有三個伙伴:Agent、Supplier A 和 Supplier B。每個伙伴服務的接口都在其 WSDL 文件中進行描述:分別為 quoteProcessClient.wsdl、SupplierAService.wsdl 和 SupplierBService.wsdl。
雙擊 quotesProcess.bpel 文件並在編輯器中打開,刪除缺省伙伴鏈接。
將 quoteProcessClient.wsdl 拖放至編輯器並接受缺省值。
在編輯器中,選擇 QuotesProcessPortType 伙伴鏈接。在 details 區域,單擊 Implementation 選項卡,並單擊 role switch icon,將 QuotesProcessPortTypeRole 設置為流程角色名。
類似地,將 SupplierAService.wsdl 和 SupplierBService.wsdl 文件拖放至編輯器;接受缺省值。
步驟 8. 定義流程變量
接下來,定義流程變量。該流程狀態信息流受流程變量管理。在 WebSphere Studio Application Developer Integrated Edition V5.1.1 中,創建的所有流程變量都是全局變量。因此,你可以從任何代碼塊訪問流程變量。各伙伴鏈接都有輸入和輸出變量,因此三對變量需要在 quotesProcess 中定義。
在 BPEL 編輯器中,刪除缺省的 InputVariable。
單擊 plus icon 並添加新的 Input 變量。
在 Details 區域,單擊 Message 選項卡,浏覽到 quoteProcessClient.wsdl 文件並將其鏈接至 getQuotesRequest 消息。
重復相同的步驟創建以下流程變量。將其鏈接至各自的消息,如以下表格所示:
Variable Message WSDL Output getQuotesResponse quoteProcessClient.wsdl SupplierAQuoteReq getQuoteRequest SupplierAService.wsdl SupplierAQuoteRes getQuoteResponse SupplierAService.wsdl SupplierBQuoteReq getQuoteSoapIn SupplierBService.wsdl SupplierBQuoteRes getQuoteSoapOut SupplierBService.wsdl
步驟 9. 執行 Receive 和 Reply 活動
接下來,執行 Receive 和 Reply 活動。Receive 活動接收 Web 服務請求,該請求啟動報價流程。通過調用 Web 服務,Reply 活動將發送報價響應給調用者。
單擊 Receive 活動。
在 Details 區域,單擊 Implementation 選項卡並將 PartnerLink 設置為 QuotesProcessPortType。設置 Operation 為 getQuotes 並將 Request 設置為 Input。
類似地,對於 Reply 活動,將 PartnerLink 設置為 QuotesProcessPortType。將 Operation 設置為 getQuotes 並將 Response 設置為 Output。
步驟 10. 創建 Flow 和 Sequence 活動
Flow 活動是一組以並行方式運行的活動,而 Sequence 是一組以串行方式運行的活動。在代理報價流程中,您希望同時調用 Supplier A 和 Supplier B 的 Web 服務。因此,創建 Flow 活動,兩個序列活動將並行運行,且每個 Sequence 活動都准備好調用,調用供應方的 Web 服務,並處理結果。在 BPEL 中,Invoke 用來執行在外部實現的業務邏輯。
從 Palette 選擇 Flow 活動並放到編輯器中,位於 Receive 和 Reply 活動之間。
從 Palette 選擇 Sequence 活動並放至 Flow 活動中。將 Sequence 活動命名為 QuoteSupplierA。
准備調用 Supplier A Web 服務所需的變量。在 Palette 上,選擇 Assign 活動並放到 QuoteSupplierA 序列活動中。命名為 Init。
在 Implementation 細節區域,復制 Input 變量 getQuotesRequest 消息的輸入部分,並粘貼到 SupplierAQuoteReq 變量 getQuoteRequest 的 productName。
將 Invoke 拖放至 QuoteSupplierA 序列活動中,並至於 Init 活動下。將其重命名為 getQuote。
在 Implementation 細節區域,將 getQuote 活動設置為伙伴鏈接 SupplierAQuotePortType,將 Operation 設置為 getQuote 並將 Request 消息設置為 SupplierAQuoteReq,並將 Response 消息設置為 SupplierAQuoteRes。
步驟 11. 創建 Invoke 活動
接下來,為 Supplier B 創建 Invoke 活動。要調用 Supplier B Web 服務,您需要使用流程變量 getSupplierBQuoteReq 的初始化 Java 片斷。您也可以使用 Java 片斷來執行簡單的業務邏輯,而無需調用外部 Web 服務。
將 Sequence 活動放到 Flow 活動中。並命名為 QuoteSupplierB。QuoteSupplierB 是 QuoteSupplierA 的並行活動。
將 Java 片斷拖至 QuoteSupplierB 活動中,並重新命名為 Init。
在 Init Java 片斷的 Implementation 細節區域中,復制並粘貼以下代碼:supplier.b.service.GetQuoteElement newValue =
new supplier.b.service.GetQuoteElement();
newValue.setQuoteItemName(getInput(true).getInput());
getSupplierBQuoteReq(true).setParameters(newValue);
將 Invoke 活動放至 QuoteSupplierB 中的 Init 下。重新命名為 getQuote。
將 getQuote 活動設置為伙伴連接 SupplierBQuoteServiceSoap,將 Operation 設置為 getQuote,將 Request 消息設置為 SupplierBQuoteReq,並將 Response 消息設置為 SupplierBQuoteRes。
現在,在 Flow 活動之後,但在 Reply 活動准備輸出前,立即放置 Java 片斷。將其命名為 preReply。
復制並粘貼以下 Implementation 細節區域中的代碼片斷作為 preReply Java 片斷。ProductQuotes newValue = new ProductQuotes();
newValue.setSupplierBQuote(getSupplierBQuoteRes(true).
getParameters().getGetQuoteResult());
newValue.setSupplierAQuote(getSupplierAQuoteRes(true).getProduct());
getOutput(true).setOutput(newValue);
現在,您已經完成了圖 10 中定義的報價流程。
圖 10. 代理 Quotes 流程
步驟 12. 生成部署代碼
保存 BPEL 文件並生成部署代碼。
右鍵單擊 quotesProcess.bpel 文件並選擇 Enterprise Services>Generate 部署代碼。
在 Generate BPEL Deploy Code 向導中,單擊 quotesProcessPortType 接口。
選擇 SOAP/HTTP 作為綁定並選擇 IBM Web Service。單擊 OK。
代理流程作為 SOAP/HTTP Web 服務來部署。QuotesProcessServiceWeb 項目中的 WSDL 和 XSD 文件公開自身接口供任何客戶端調用流程。
圖 11. 代理流程的 WSDL 和 XSD 文件
創建 Java 客戶端代理與代理流程連接
在本部分,您可以使用圖 11 中的流程 WSDL 和 XSD 文件,生成在業務場景中由購買者調用的 Java 客戶端代理,如圖 1 所示。
首先,創建 Java 項目 QuoteProcessTestClient 並從 QuoteProcessServiceWeb 復制 WSDL 以及三份 XSD 文件至測試客戶端項目。
接下來,從 WSDL 生成 JAX-RPC 客戶端代理。
右鍵單擊 QuotesProcess_QuotesProcessPortType_HTTP.wsdl 文件並選擇 Enterprise Services>Generate Service Proxy。
在向導中,選擇 Java API for XML-based RPC (JAX-RPC) 作為代理類型,單擊 Next 並單擊 Next 繼續。
請確保選擇 Java 作為 Client 類型以及選擇 QuoteProcessTestClient 作為客戶端項目。單擊 Next。
在接下來的頁面中,接受所有的缺省選項並單擊 Finish。
JAX-RPC 代理的類設置已經生成,如圖 12 所示。testclient 包中的 Buyer.java 將在下個章節進行介紹。
圖 12.代理流程的 JAX-RPC 代理
測試代理流程
要測試代理流程,首先要創建測試服務器並在其上部署 Supplier A Web 服務和代理流程。
切換到 Server 視圖。創建新的集成測試服務器,配置並將其命名為 TestServer。
在 Servers 窗口,右鍵單擊 TestServer。單擊 Add 和 Remove Projects。
在 TestServer 上添加兩個項目:SupplierAServiceEAR 和 QuoteProcessServiceEAR。單擊 Finish。
右鍵單擊 TestServer 並單擊 Start。
接下來,實現描述 Buyer 的主要類,通過 JAX-RPC 代理來調用代理流程。
創建新的 Java 包。右鍵單擊 QuoteProcessTestClient,選擇 New>Package,為新 Java 包命名為 testclient。單擊 Finish。
創建 Buyer 類,將其命名為 Buyer。單擊 Finish。將打開 Buyer 類。
將清單 8 中的代碼復制並粘貼至 Buyer 類編輯器並保存 Java 文件。
清單 8.Buyer 類
package testclient; import java.util.Calendar; import java.util.Date; import process.quote.QuotesProcessPortTypeProxy; import supplier.b.schema.DateInfo; import com.ibm.www.ProductQuotes; public class Buyer { public static void main(String[] args) { String product = "IBM ThinkPad T40"; QuotesProcessPortTypeProxy aProxy = new QuotesProcessPortTypeProxy(); try { ProductQuotes result = aProxy.getQuotes(product); supplier.a.schema.Product quoteA = result.getSupplierAQuote(); supplier.b.schema.Product quoteB = result.getSupplierBQuote(); System.out.println("Quotes for product: " + product); System.out.println("\tSupplier A: "); System.out.println("\t\tQuantity: " + quoteA.get_qty()); System.out.println("\t\tPrice: " + quoteA.get_price()); System.out.println( "\t\tIs refurbished: " + quoteA.is_refurbished()); System.out.println("\tSupplier B: "); System.out.println("\t\tQuantity: " + quoteB.get_qty()); System.out.println("\t\tPrice: " + quoteB.get_price()); DateInfo[] dates = quoteB.get_dates().get_dateInfo(); for (int i = 0; i < dates.length; i++) { Calendar cal = dates[i].get_date(); Date date = cal.getTime(); System.out.println("\t\t" + dates[i].get_desc() + ": " + date); } } catch (Exception e) { e.printStackTrace(); } } }
最後,運行 Buyer 類獲取 Supplier A 和 Supplier B 提供的關於 IBM ThinkPad T40 的產品信息。
選擇 Package Explorer 中的 Buyer 類。
在頂端的菜單中,選擇 Run>Run As>Java Application。如果順利完成,結果將出現在控制台中,如以下的 圖 13 所示。
圖 13. 報價結果
對比圖 13 和圖 7 中的單元測試結果,並觀察兩種情況下如何描述日期信息數組。
在代理流程及其客戶端代理類中,xsd:dateTime 被映射至 java.util.Calendar,但是最好能呈現給購買者的是簡潔的 java.util.Dates 結果而不是包含大量多余信息的 java.util.Calendar。如果客戶端需要 java.util.Dates ,那麼需要對其進行簡單轉換,如清單 8 所示。
其它技巧
以下是針對 J2EE 和 .NET 開發 BPEL 流程的一些其他技巧:
WebSphere Studio Application Developer Integration Edition Version 5.1.1 提供了強大的可視流程調試器,可以在 BPEL 流程級別上逐步調試代碼。
對於在 Web 服務中來去的 SOAP 消息,你需要對其進行截取並研究,特別是 .NET Web 服務中的 SOAP 消息。可用的跟蹤工具有很多。WebSphere 提供實體類 com.ibm.ws.webservice.engine.utils.tcpmon 用以嗅探兩點之間的 HTTP 通信。您可以隨意選擇您熟悉的跟蹤工具。
在大多數情況下,開啟服務器跟蹤查找異常的根源是十分必要的。
結束語
通過 BPEL 進行業務流程集成的成敗關鍵在於參與該流程的 Web 服務的內在互操作性。文中的技巧著重強調了在設計消息模式時必須十分謹慎,且該技巧還演示了無論是哪種平台(J2EE 或是 .NET),簡單 Web 服務和復雜 Web 服務都可以成功地參與業務流程。WebSphere Studio Application Developer Integrated Edition V5.1.1 提供了強大的 BPEL 流程開發環境、方便的 XML Schema 和 WSDL 設計工具。
本文配套源碼