程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> WebSphere >> 通過Web服務API和JAXB編組與WebSphere Process Server交互

通過Web服務API和JAXB編組與WebSphere Process Server交互

編輯:WebSphere

簡介

除了傳統的 Enterprise JavaBeans (EJB) 接口之外,最近增加了 Java Messaging Service (JMS) API、REST 接口和 Web 服務 API。comparison of the programming interfaces for interacting with business processes and human tasks 討論了這些 API 的優點和缺點。

Web 服務 API 是在 WebSphere Process Server 的 6.0.2 版中引入的,它為構建與業務流程和人工任務交互的客戶機應用程序提供豐富的功能。具體地說,它提供以下功能:

能夠與支持 Web 服務調用的任何運行時環境通信,包括 Microsoft®.NET 環境。

作為 Web 服務公開底層的調用點。

更好地隔離客戶機和服務器。

可以利用現有的行業模式以及強大的 XML 工具和運行時。

使用 EJB API 時,可以利用 遠程工件裝載器 從遠程服務器將現有的工件裝載到應用程序中。它宿主著服務器上安裝的工件,讓它們對相同或其他計算單元中的遠程工件裝載器客戶機可用。客戶機然後可以從遠程工件裝載器服務器查詢或裝載工件。

但是,Web 服務 API 不支持 RAL,所以在客戶機上必須有適當格式的輸入數據模式、輸出數據模式和變量模式。對於這個問題,Java Architecture for XML Binding (JAXB) 提供一種把 XML 模式綁定到 Java 代碼中的表示的簡便方法。這讓開發人員可以方便地把 XML 數據和處理功能合並到 Java 應用程序中,而不必詳細了解 XML 本身。

本文並不是 JAXB 教程,而是討論 JAXB 開發和運行時環境如何簡化把 XML 模式定義 (XSD) 映射到 Java 的過程。本文還討論在運行時通過 JAXB 運行時和 Java 反射動態地生成基本用戶界面所需的運行時特性。

Java Architecture for XML Binding (JAXB)

可以通過 XML 模式表示業務領域對象和它們的結構關系。JAXB 引入了數據綁定 的概念,即 XML 模式與 Java 類的對應關系。

JAXB 模式編譯器根據 XML 模式的結構創建 Java 類和接口(通常在開發時執行)。在運行時,使用 JAXB 庫進行編組和反編組。編組(marshalling) 是把一個或多個 Java 對象轉換為 XML 文檔的過程,反編組(unmarshalling) 是相反的過程,即根據 XML 創建 Java 對象,見圖 1。

圖 1. JAXB 元素

自頂向下還是自下而上?

可以使用兩種方式創建 Web 服務:自頂向下開發(即根據 WSDL 文件創建服務),以及自下而上開發(即根據現有的 Java 類創建所需的 XML 工件)。JAXB 支持這兩種方法,但是推薦自頂向下方法,因為這種方法具有跨平台、綁定和編程語言的互操作性,更為方便。

xjc 模式編譯器根據 XML 模式生成帶注解的 Java 類。這個步驟通常在開發時執行,它創建一組映射到 XSD 文件中定義的元素和類型的 JavaBean。然後,通過使用 JAXB 綁定運行時 API,在 XML 實例文檔和 Java 對象之間進行轉換。

帶注解的類包含 JAXB 運行時解析 XML(為了進行編組和反編組)所需的所有信息,還可以通過一個可選的步驟檢驗文檔。可以把生成的類與 Java API for XML Web Services (JAX-WS) 技術和 WebSphere Process Server Web 服務 API 結合使用。

JAXB 的優點在於,它讓 Java 開發人員可以訪問和處理 XML 模式,而不必詳細了解底層機制。另外,可以靈活地定制模式如何映射到 Java(以及反向映射),這對於復雜或經常變動的模式尤其有用,因為同步相應的 Java 定義要花費大量時間,很容易出錯。

在下面幾節中,討論與開發和運行時相關的活動。但是,在討論細節之前,我們先簡要介紹一下主要構建塊。

基本架構

圖 2 給出一個可能出現的場景:浏覽器訪問部署在兩個服務器上的客戶機應用程序和後端 WebSphere Process Server。

圖 2. 主要構建塊

客戶機邏輯通過代理與過程通信,代理把 Business Flow Manager (BFMIFProxy.java) 和 Human Task Manager (HTMIFProxy.java) Web 服務操作表示為 Java 接口。它們是在設置開發環境時根據導出的 WSDL 工件生成的。

跨服務器的通信必須確保安全。所有 Web 服務請求必須包含安全令牌,安全令牌代表有效的用戶身份驗證。Web 服務 API 支持的安全機制是用戶名令牌 和 Lightweight Third Party Authentication (LTPA)。前提條件是需要確保 Web 應用程序的安全,因此它需要顯式的身份驗證。對這個設置過程的詳細描述,參見 Web service API - J2EE client 中的安全部分。

另外,如果客戶機應用程序在單獨的邏輯節點上運行,那麼必須在這兩個服務器之間建立單點登錄,從而確保每個調用都附帶 LTPA 令牌。

開發活動

下面討論開發時所需的步驟。

設置 Web 服務開發環境

使用 WebSphere Process Server Web 服務 API 涉及許多步驟,比如復制關鍵工件、生成代理客戶機和確保安全性。Web 服務代理是應用程序的主要集成點,是在 WebSphere Integration Developer 中根據導出的 WSDL 工件生成的,見圖 3。

圖 3. 生成 Web 服務代理

Developing client applications 和 Web service API - J2EE client - Version 7.0 解釋了這些步驟。既然已經准備好了基本的開發環境,我們就來看看另一個主要構建塊 JAXB。

示例模式

正如前面提到的,創建 Web 服務的最佳實踐是自頂向下方法,即根據 WSDL 和模式創建 Web 服務。對於本文,我們使用一個大家都了解但不一定喜歡的模式類型:稅收模式。圖 4 給出 WebSphere Integration Developer 模式編輯器中的主要類型。

圖 4. 示例 XML 模式

XML 模式代表數據或信息模型,因此是所有軟件開發項目中的關鍵工件。XML 模式往往是解決方案的主要相關人員進行徹底討論和分析的結果。它必須與過程和領域模型及用例一起維護。

在早期設計和開發階段,模式很可能會發生變化,相應的 Java 類必須反映這些變化。可以使用 JAXB 簡化這個映射過程。

另外,您可能無法直接控制模式,因為它們是由其他組織定義或由專有工具(比如 WebSphere Business Modeler)生成的。在這些情況下,您只能去適應模式,使它們 “映射更友好”,例如采用能夠明確地映射到 Java 類的形式。JAXB 提供強大的定制功能,讓這個過程更輕松。

綁定模式文檔

用命令行工具 xjc 或在 WebSphere Integration Developer 環境中直接生成 JAXB 綁定類。要想生成相應的 Java 類,進入 J2EE 透視圖,右鍵單擊模式文檔並選擇 Generate > Java,見圖 5。

圖 5. 生成 Java 類

在後面的向導中,選擇 JAXB Schema to Java Bean 並單擊 Next。"XSD to Java" 屏幕出現,見圖 6。

圖 6. XSD to Java 向導

這個面板提供以下選項:

Generate schema library 允許定制模式文件到項目的映射(在下一個面板上)。

在 Target Java Container 列表中,指定用於包含生成的 Java bean 的項目或文件夾。

在 Target Package 框中,輸入 Java 包的名稱或接受默認值。

一個重要的選項是 Binding Files,它定制 XML 模式組件與其 Java 表示之間的默認映射。在 Binding Files 面板上單擊 Add,就可以選擇外部綁定聲明文件。根據約定,綁定文件的擴展名是 .xjb,可以使用任何文本編輯器創建。後面討論這些文件的格式和內容。

單擊 Finish。

JAXB 綁定編譯器把 XML 模式轉換為一組相應的 Java 類,它們與模式中描述的結構匹配。它們包含 JAXB 運行時環境解析和重建原 XML 表示所需的所有信息。這顯著簡化了數據編程模型,讓開發人員可以簡便地執行對象實例化以及使用 getter 和 setter 方法。不需要通過編寫代碼在 XML 格式和 Java 應用程序之間進行數據轉換。

通過考察 “Case” 類型詳細看看這個映射,見圖 7。

圖 7. Case XSD 類型

清單 1. Case XSD 源代碼

<xsd:complexType name="Case">
   <xsd:sequence>
    <xsd:element maxOccurs="1" minOccurs="1" name="Number"
    type="xsd:string" />
    <xsd:element maxOccurs="1" minOccurs="1" name="Amount"
    type="xsd:decimal" />
    <xsd:element maxOccurs="1" minOccurs="1" name="Created"
    type="xsd:date" />
    <xsd:element maxOccurs="1" minOccurs="1" name="Sequence"
    type="xsd:integer" />
    <xsd:element name="Status">
    <xsd:simpleType>
     <xsd:restriction base="xsd:string">
     <xsd:enumeration value="Open" />
     <xsd:enumeration value="Closed" />
     <xsd:enumeration value="Forwarded" />
     </xsd:restriction>
    </xsd:simpleType>
    </xsd:element>
    <xsd:element name="Product">
    <xsd:simpleType>
     <xsd:restriction base="xsd:string">
     <xsd:enumeration value="IncomeTax" />
     <xsd:enumeration value="BusinessTax" />
     </xsd:restriction>
    </xsd:simpleType>
    </xsd:element>
       <xsd:element name="Approved" type="xsd:boolean"></xsd:element>
       <xsd:element maxOccurs="1" minOccurs="1" name="Taxation"
    type="bo:Taxation" />
    <xsd:element maxOccurs="1" minOccurs="0" name="Objection"
    type="bo:Objection" />
    <xsd:element maxOccurs="unbounded" minOccurs="0" name="History"
    type="bo:Reference" />
   </xsd:sequence>
   </xsd:complexType>

從模式到 Java 類的 默認數據類型綁定 基本上符合預期。清單 1 中的模式生成清單 2 所示的 Java 代碼(為了便於閱讀,刪除了注解)。

清單 2. Case Java 類

public
   class Case {
   protected String number;
   protected BigDecimal amount;
   protected XMLGregorianCalendar created;
   protected BigInteger sequence;
   protected String status;
   protected String product;
   protected boolean approved;
   protected Taxation taxation;
   protected Objection objection;
   protected List<Reference> history;

// Getter and Setter

}

大多數類型映射到同名的 Java 簡單類型。明顯的例外是小數和整數類型。它們與未指定小數范圍和總位數的數字匹配,最好的 Java 匹配分別是 java.math.BigDecimalK、java.math.BigInteger 和 javax.xml.datatype.XMLGregorianCalendar。

JAX-WS 與 JAXB 的關系

JAX-WS 使用 JAXB 作為默認的綁定機制,可以用這裡討論的方法定制綁定。另外,還可以通過綁定聲明控制 WSDL 到 Java 的映射。可以定制幾乎所有可以映射到 Java 的 WSDL 組件,比如服務端點接口類、方法名、參數名、異常類等等。更多信息參見 JAX-WS WSDL Customization。

盡管一些處理金額的應用程序需要很高的精度,但是您可能希望使用簡單的整數序列,這更便於在代碼中處理。最容易的修改方法是使用內置的 xsd:int 類型修改 XSD 類型,或者引入具有適當限制的新的 xsd:simpleType。

常見的其他需求包括覆蓋默認的綁定規則(由於包名等命名需求或命名沖突)、覆蓋默認的映射 enums 以使它們類型安全或者添加文檔。一種實現方法是 內聯定制。

但是,這些方法都要求您能夠直接控制模式。如果不是這種情況,應該使用外部定制。

ObjectFactory 可以方便地創建組成樹中子元素的對象。根據定義,新創建的 JAXB 對象是 “淺對象”;也就是說,如果它的樹結構中包含復雜的類型,那麼並不創建這些對象,它們僅僅是 null。元素對象工廠的方法或 Java 類的構造器都不提供標准的樹構建算法。這在構造樹的方式方面提供了靈活性,但是也意味著由您負責構造樹。我們將在 創建子樹 一節中詳細討論這個主題。

外部綁定定制

如果無法直接控制 XSD 文件、要處理的模式文檔很大或者只是為了清晰起見想分為兩個文件,那麼可以把綁定規則放在單獨的外部文件中。綁定定制文件也是 XML 模式文檔,擴展名通常是 .xjb。定制文件的一般語法見清單 3。

清單 3. 綁定文件語法

<jaxb:bindings schemaLocation = "xsd:anyURI">
   <jaxb:bindings schemaLocation=”file.xsd” node="xsd:string">
    <binding declaration>
   <jaxb:bindings>
</jaxb:bindings>

其中:

schemaLocation 引用遠程模式。

node 是一個 XPath 1.0 表達式,它指定與給定的綁定聲明相關聯的模式節點(對 XPath 的詳細說明參見 XML Path Language)。

<binding declaration> 控制如何生成 JAXB 數據綁定工件。

綁定聲明與一個范圍相關聯,見圖 8。

圖 8. 綁定范圍

詳細討論參見 Customization syntax。假設希望把生成的類的名稱由 “Case” 改為 “Special Case”,見清單 4。

清單 4. 定制示例

<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         jaxb:version="2.0">
  <jaxb:bindings schemaLocation="Businessitems.xsd" node="/xsd:schema">
   <jaxb:bindings node="xsd:complexType[@name='Case']">
       <jaxb:class name="SpecialCase" />
   </jaxb:bindings>
  </jaxb:bindings>
</jaxb:bindings>

現在,使用 types.xjb 綁定文件消除比較怪的 Java 類型,這很簡單,見清單 5。

清單 5. 類型定制

<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         jaxb:version="2.0">
  <jaxb:bindings schemaLocation="Businessitems.xsd" node="/xsd:schema">
   <jaxb:globalBindings>
    <jaxb:javaType name="java.lang.Float" xmlType="xsd:decimal" />
      <jaxb:javaType name="java.lang.Integer" xmlType="xsd:integer" />
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
      <jaxb:javaType name="java.util.Date" xmlType="xsd:date" />
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
   </jaxb:globalBindings>
  </jaxb:bindings>
</jaxb:bindings>

同樣,可以把 XSD 枚舉轉換為類型安全的 Java enum 類型,見清單 6。

清單 6. 枚舉定制

<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         jaxb:version="2.0">
  <jaxb:bindings schemaLocation="Businessitems.xsd">
   <jaxb:bindings node="//xsd:element[@name='Status']/xsd:simpleType">
     <jaxb:typesafeEnumClass name="Status"/>
   </jaxb:bindings>
   <jaxb:bindings node="//xsd:element[@name='Product']/xsd:simpleType">
     <jaxb:typesafeEnumClass name="Product"/>
   </jaxb:bindings>
  </jaxb:bindings>
</jaxb:bindings>

用綁定文件 enums.xjb 和 types.xjb 重新生成 Java 類,見圖 9。

圖 9. XSD to Java 向導和綁定文件

這會創建包含定制類型的 Java 類和類型安全的 enum Java 類,見清單 7。

清單 7. 定制的 Java 類

public
   class Case
{
   protected String number;
   protected Float amount;
   protected Date created;
   protected Integer sequence;
   protected Case.Status status;
   protected Case.Product product;
   protected
   boolean approved;

   public
   enum Status
{
     OPEN("Open"),
     CLOSED("Closed"),
     FORWARDED("Forwarded");
     private
   final String value;

}

注解

生成的類包含特殊的 JAXB 注解,它們向運行時框架提供處理相應 XML 文檔所需的映射。反向映射以及覆蓋 Java 到 XML 模式映射的默認綁定規則也需要注解。可以使用 javax.xml.bind.annotation 包中的所有注解。

關鍵的注解是 @XmlRootElement,它把頂級類映射到 Web 服務的 WSDL 使用的 XML 模式中的全局元素。但是,有時候 JAXB 編譯器在猜測哪些類應該有這個注解時似乎過分聰明了。如果發生這種情況,就會顯示下面的運行時錯誤消息:

Unable to marshal type "gov.mof.tax.businessitems.Case" as an element because it
is missing an @XmlRootElement annotation.

因此,問題是:why does JAXB put @XmlRootElement sometimes, but not always?(為什麼 JAXB 有時候放 @XmlRootElement 注解,有時候不放?) 這個帖子很有意思,但是仍然沒有得到適當的解答。這個問題很煩人,因為可能經常要生成 Java 類,需要進行手工編輯以適當解決此問題。同樣,可以使用外部綁定文件來解決,方法參見 How to make XJC generate XmlRootElement with an external binding file。

多態的行為

我們的 XSD 使用了一個有意思的 XML 模式特性 —— 繼承。文章 Polymorphic Web services 詳細介紹了這個特性,解釋了 XML 擴展和動態服務調用技術如何提供多態性。

我們在 JAXB 的上下文中考慮一下圖 10 所示的模式。

圖 10. XML 模式中的繼承

BaseNotification 分別派生出 TaxNotification 和 PaymentNotification。它們繼承基類型的所有屬性和元素,並添加自己的新屬性,見清單 8。

清單 8. XML 模式中的繼承

<xsd:complexType abstract="true" name="BaseNotification">
  …
</xsd:complexType>

<xsd:complexType name="TaxNotification">
  <xsd:complexContent>
   <xsd:extension base="bo:BaseNotification">
   </xsd:extension>
  </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="PaymentNotification">
  <xsd:complexContent>
   <xsd:extension base="bo:BaseNotification">
...

JAXB 運行時通過內省注冊所需的所有 Java 類。現在,如果在運行時作為基類型傳遞派生的子類,那麼無法通過檢查接口參數類型識別出子類。

為了利用繼承和多態性,在 JAXB 2.1 和 Java 6.0 中引入了一個新注解 javax.xml.bind.annotation.XmlSeeAlso。@XmlSeeAlso 通過引用在 JAXBContext 中添加的額外派生子類類以及根據接口參數識別出的類實現多態性,見清單 9。

清單 9. 包含繼承的 Java 類

@XmlSeeAlso({
   PaymentNotification.class,
   TaxNotification.class
})
public
   abstract
   class BaseNotification {
   ...

運行時

完成基本設置之後,我們來看看如何讓 JAXB 起作用。我們需要一個基本的業務流程,以便演示如何處理人工任務,見圖 11。

圖 11. 簡單業務流程

把這個業務流程導入 WebSphere Integration Developer 並部署到 WebSphere Process Server 中。可以在本文提供的 sample_WID_project.zip 文件中找到 "TaxClient"。TaxClient 是一個 Web 項目,包含簡單的用戶界面,見圖 12。

圖 12. 基本用戶界面

設置端點

首先,需要配置 Java Web 服務代理使用的 Web 服務端點。例如,JSP 中使用的一個 JavaBean 可以表示代理設置,可以在整個項目中共享它,見清單 10。

清單 10. 設置 Web 代理端點

<% 
  String bfmEndpoint = ServicesBean.getInstance().getBfmEndpoint();
  String htmEndpoint = ServicesBean.getInstance().getHtmEndpoint();

  if( request.getParameter( "newBFMEndpoint" ) != null )
  {
    bfmEndpoint = request.getParameter( "newBFMEndpoint" );
    ServicesBean.getInstance().setBfmEndpoint( bfmEndpoint );
  }
  if( request.getParameter( "newHTMEndpoint" ) != null )
  {
    htmEndpoint = request.getParameter( "newHTMEndpoint" );
    ServicesBean.getInstance().setHtmEndpoint( htmEndpoint );
  }
  %>

然後,創建實際的運行時代理並保存在 Java Bean 中。在 Business Process Choreographer Explorer 中創建幾個業務流程實例之後,現在可以接收並處理與人工任務相關的數據。

接收源數據

JAXBContext 類管理所有後續操作所需的綁定關系,這是 Java 應用程序的入口點。使用清單 11 所示的代碼獲取這個類的新實例。

清單 11. 創建 JAXB 上下文

JAXBContext jaxbContext = 
JAXBContext.newInstance( "gov.mof.tax.businessitems" );

這個調用初始化 JAXBContext 對象以管理運行時調用。傳遞的參數包含以冒號 (:) 分隔的 Java 包名列表(這些包包含從模式派生的類)。但是,JAXBContext.newInstance 調用的開銷很大。因為根據設計它是線程安全的,創建它之後可以把它緩存在一個公共靜態最終變量中,或者包裝在單實例模式中以供以後訪問。

現在要創建另一個主要運行時工件,反編組器。可以通過它把 XML 數據直接轉換為 Java 內容對象樹,見圖 13。

圖 13. 反編組器的作用

如果通過 @XmlSeeAlso 注解標為實例文檔的根,就可以對任何全局 XML 元素進行反編組。清單 12 中的代碼片段說明如何創建反編組器。如果在 contextPath 中列出,還可以跨一組模式合並全局元素。

清單 12. 創建反編組器

Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

反編組器可以處理來自各種數據源的 XML 數據,包括文件、輸入流、URL、DOM 對象、SAX 解析器等。在我們的示例中,數據源是通過對 WebSphere Process Server Human Task Manager (HTM) 代理執行的 claim() 調用返回的,見清單 13。

清單 13. 對數據源執行反編組

Claim claim = new Claim( );
claim.setTkiid( tkiid ); // returned via FacesContext Request Parameter
ClaimResponse response = htmProxy.claim( claim );
SOAPElement element = response.get_any();

// Traverse to SDO node
Iterator<SOAPElement> iter = element.getChildElements();
if( iter.hasNext())
{
  SOAPElement operation = iter.next();
  Class<?> inputClass = … // via introspection from JAX-WS WSDL 
  unmarshaller.setValidating( true );
  JAXBElement<?> jaxbObject = unmarshaller.unmarshal( operation,
  inputClass );
  inputObject = jaxbObject.getValue();
  if( inputObject instanceof inputClass )
  {
   ...
  }
  Iterator<SOAPElement> iterDetails = operation.getChildElements( );
  while( iterDetails.hasNext())
  {
   SOAPElement detail = iterDetails.next();
   System.out.println( "Input " + detail.getLocalName());
   ...
  } // while
} // if

WebSphere Process Server HTM API 通過 claim() 調用返回一個人工任務的當前輸入數據。這個調用的輸入是一個引用特定人工任務實例的 tkiid(任務實例 ID),它返回輸入數據。在我們的示例中,當用戶從任務列表中選擇一項時,作為隱藏的字段存儲 tkiid,然後由客戶機返回它。

因為返回的 SOAPElement 在底層具有對象層次結構,所以必須遞歸地處理樹以獲得實際的數據對象。通過對 JAX-WS WSDL 工件的內省,解析預期的 inputClass。為了安全起見,檢查返回的對象的實例類型。

這段代碼說明了 JAXB 是多麼優雅、強大:一旦准備好所有工件,就很容易接收並反編組任何 XML 模式/Java 類型,不需要了解模式映射的底層機制。

檢驗源數據

JAXB 的有用特性之一是,在反編組操作過程中可以根據相關聯的模式檢驗源數據。

清單 14. 檢驗源數據

unmarshaller.setValidating( true );

清單 14 中的這一行代碼讓 JAXB 運行時在數據不符合模式的情況下發出報告。規范在這個方面不明確:它要求實現報告檢驗錯誤,但是並不強制停止數據處理。這意味著沒有嚴格地定義 JAXB 實現處理無效 XML 文檔的方式。

根據常理,實現必須能夠反編組有效的文檔。注意,如果啟用了檢驗,就會檢查 XML 模式 facets,它限制基本數據類型。

創建子樹

一旦得到任意復雜度的樹的根對象,就可以開始使用它了。但是正如前面指出的,如果這是第一次調用,或者以前沒有設置值,樹可能還沒有完全構造好。

在內存中 “預構建” Java 對象的一種方法是通過反射在樹中移動,遞歸地解析包含的復雜類型。如果模式檢驗沒有提供默認值,還可以設置默認值,見清單 15。

清單 15. 構建 Java 對象

public void buildDataObject( Object dataObject)
{
  …
  Method[] methods = dataObject.getClass( ).getMethods();
  for( Method method : methods )
  {
   String methodName = method.getName();
   // Setters start with "set" and have one param
   if( methodName.startsWith( "set" ))
   {
    Class[] parameters = method.getParameterTypes();
    // Check if the value is already set through the getter, (boolean ‘is')
    byte[] byteArray = methodName.getBytes();
    String getterName = new String( byteArray );

    if( parameters[0] == java.lang.Boolean.class ||
    parameters[0] == java.lang.Boolean.TYPE )
    {
     getterName = methodName.replaceFirst( "set", "is" );
    }
    else // turn 'setter' into 'getter'
    {
     byteArray[0] = 'g';
     getterName  = new String( byteArray );
    }
    Method getter = dataObject.getClass().getMethod( getterName, new Class[]{} );
    Object result = getter.invoke( dataObject, new Object[]{} );
    if( parameters.length == 1 )
    {
     Class parameter = parameters[0];
     if( result != null )
     {
      // If the value is already set, skip
      if( parameter == java.lang.Integer.class ||
       …
        parameter == java.lang.Enum.class )
      {
       continue;
      }
      // If this is a complex type, recurse
      buildDataObject( result );
      continue;
     }
...

這個方便的方法從樹根開始一直處理到樹葉,構造並填充樹。這是一種相當常見的模式。也可能有由其他系統提供的預先構建的部分樹。很容易使用清單 15 所示的方法添加它們。

動態地顯示模式

我們已經討論了必須適應經常變化的 XSD,看到了 JAXB 如何幫助滿足這一需求。另外,開發功能大致相同的動態用戶界面也有意義:通過反射遍歷 Java 對象,動態地創建代表相應輸入框的 HTML 代碼。

然後,可以收集用戶輸入,把輸入發送回 WebSphere Process Server。我們先考慮清單 16 中的代碼片段,它說明如何構建這樣的動態呈現器。

為了了解服務的輸入和輸出數據類型,要引用 WSDL。一種方法是創建一個 Java 客戶機,然後對創建的 Java 類進行內省。為此,找到 WSDL 並創建客戶機,見圖 14。

圖 14. 創建 WSDL 客戶機

清單 16 中的代碼片段說明如何讀取元數據。

清單 16. 讀取 WSDL 元數據


// Retrieve Helper for input
// The standard 'getTypeDesc()' method provides the WSDL metadata

Class<?> helper = Class.forName( wsdlPackage + "." + operationName + "_Helper");
Method  method = helper.getMethod( "getTypeDesc", new Class[]{} );
TypeDesc typeDesc = (TypeDesc)method.invoke( null, new Object[]{} );
FieldDesc[] fieldDesc = typeDesc.getFields();

for( FieldDesc field : fieldDesc )
{

inputName = field.getXmlName().getLocalPart();
inputClass = Class.forName( inputPackage + "." + field.getXmlType().getLocalPart());

}
// Retrieve Helper for output (Response)
...

參見 sample_WID_project.zip 文件中提供的 TaxClient 項目的 gov.mof.tax.bean 包。

清單 17. 創建表單 (1)

protected void createFormFromDataObject( Object dataObject,
    boolean disabled )
{
  // Custom logic to check if data objects have equal types, etc
  …
  delegate.beginRendering( );
  createForm( dataObject, null, "", htmlString, disabled);
}

用一個數據對象調用清單 17 中的主要方法 createFormFromDataObject,這個數據對象代表人工任務的輸入和輸出。在真實的場景中,比較復雜的人工任務可能使用不同的類型。為了處理所有類型的人工任務,需要支持數量不限的輸入和輸出對象。

另外,這個方法可以通過定制的邏輯檢查是否可以使用來自輸入對象的數據預填充輸出對象。如果您願意多下功夫,這個方法可以相當復雜。然後,這個方法調用 createForm,後者通過反射遍歷樹以執行實際工作。如果找到子樹,遞歸地調用其本身,見清單 18。

清單 18. 創建表單 (2)

private void createForm( Object dataObject,
              String title,
              String methodName,
              StringBuffer htmlString,
              boolean disabled )
{
  …
  Method[] methods = dataObject.getClass( ).getMethods();
  for( Method method : methods )
  {
   methodName = method.getName();
   Class[] parameters = method.getParameterTypes();
   String label;
   // Getters start with "get" or "is" (booleans) and have no param
   if( parameters.length == 0 &&
     (methodName.startsWith( "get") || methodName.startsWith( "is" )))
   {
    …
    // Need setter when the values are retrieved from the form
    byte[] byteArray = methodName.getBytes();
    byteArray[0] = 's';
    methodName = new String( byteArray );

    Object result = method.invoke( dataObject, new Object[0] );
    if( result instanceof java.lang.Integer ||
      …
      result instanceof java.lang.Enum )
    {
     delegate.addField( label, result, currentSDOName, methodName, disabled );
    }
    else // if nested, recurse
    {
     createForm( result, label, methodName, htmlString, disabled );
    }
   } // if
  } // for

人工任務可能處於不同的狀態。如果狀態不是 “claimed”,可以把字段呈現為禁用的。一旦用戶聲明了人工任務,就啟用字段,用戶可以開始輸入數據。

為了簡化創建標記語言的過程,Delegate 類負責生成實際的 HTML 代碼。這意味著 Delegate 將封裝生成用戶界面的邏輯,而上面的 “解釋器” 將保持穩定。Delegate 維護元素的 Map,當需要呈現樹中新的 Java 類或字段時,調用這個 Map,見清單 19。

清單 19. 委托 HMTL 創建

public class Delegate
{
  private String processTemplate;
  private Map<String, StringBuffer> fieldMap;

  …
  protected void beginRendering( )
  {
   fieldMap = new TreeMap( );
  }

  protected void addField( String fieldName,
    Object fieldValue,
    String sdoName,
    String methodName,
    boolean disabled )
  {
   StringBuffer htmlBuffer = new StringBuffer( "" );
   if( fieldValue instanceof java.lang.Integer ||
     fieldValue instanceof java.lang.Float ||
     fieldValue instanceof java.lang.String )
   {
    htmlBuffer.append( "<tr><td>" + fieldName + ":</td><td>” +
     "<input name='" + sdoName + "." + methodName +
     "' size='20' value='" + fieldValue + "'" + (disabled? " disabled" : "") +
     "/></td></tr>\n" );
   }
   else if( fieldValue instanceof java.lang.Boolean )
   {
    …
   }
   else if( fieldValue instanceof java.lang.Enum )
   {
    htmlBuffer.append( "<tr><td>" + fieldName +
     ": </td><td><select name='" + sdoName + "." +
       methodName + "' " +
      (disabled? " disabled" : "" ) + " >\n" );
    Object[] enumConstants = fieldValue.getClass().getEnumConstants();
    for( Object enumConstant : enumConstants )
    {
     Object enumValue = null;
     Method valueMethod = enumConstant.getClass().getMethod( "value",
      new Class[]{} );
     enumValue = valueMethod.invoke( enumConstant, new Object[]{} );
     htmlBuffer.append( "<option value='" + enumValue + "'>" +
      enumValue + "</option>\n" );
    }
    htmlBuffer.append( "</select></td></tr>\n" );
   }
   StringBuffer sb;
   if(( sb = fieldMap.get( sdoName )) != null )
   {
    sb.append( htmlBuffer );
   }
   else
   {
    fieldMap.put( sdoName, htmlBuffer );
   }
  }

代碼很簡單,它為表單中的字段創建 HTML 輸出。但是,重要的是表單元素的 fieldNames。當把輸入發送回 WebSphere Process Server Human Task Manager 時,需要通過它們收集用戶數據。

然後,在 JSP 或 JSF 中呈現最終的標記,見清單 20。

清單 20. 在 JSP 中呈現 HMTL 代碼

<jsp:useBean id="td" scope="session" class="gov.mof.tax.bean.ToDoTaskBean" />
</head>

<body bgcolor="#efefef">
  <div class="formblock">
   <form>
    <% td.createInputForm(); %>
    <% out.print( td.getHtmlString() ); %>
    <h2>Possible Activities:</h2>
    <% if( td.getCompleteSuccessful() ) { %>
     <input id="close" type="submit" name="close" value="Close Window" />
    <% } else { %>
     <input id="claim" type="submit" name="claim" value="Claim"
         <%=(td.getClaimButton() == true) ? "" : "disabled"%> />
     <input id="cancel" type="submit" name="cancel" value="Cancel"
         <%=(td.getClaimButton() == false) ? "" : "disabled"%> />
     <input id="complete" type="submit" name="complete"
value="Complete" <%=(td.getClaimButton() == false) ? "" : "disabled"%> />
     <input id="tkiid" type="hidden" name="tkiid" value="<%=td.getTkiid()%>" />
    <% } %>
   </form>
  </div>
</body>

結果見圖 15。

圖 15. 動態的前端

字段的類型符合 XML 模式。Created 呈現為日期框,由右邊的圖標表示。在定制文件的幫助下,模式枚舉(比如 Status 和 Product)映射到 Java enums,代碼可以把它們呈現為下拉菜單。Approved 屬性映射到 Java Boolean,呈現為復選框。

如果使用相當復雜的 XSD,所有字段會顯示在一個相當混亂的表單上。使用這樣的表單很困難,很容易引起混淆。因此,我們使用 Armel Pingault 的 apTabs JavaScript 庫,它允許把表單分割為多個選項卡。

為了發送回數據,必須捕捉用戶輸入並調用完成方法。因此,我們提供所有字段的完全限定名稱,這些名稱與相應的 XPath 地址相似,見清單 21(為了便於閱讀,增加了縮進)。

清單 21. 顯示並捕捉輸入數據的 HTML 代碼

...
<table border=0 bgcolor='#f2f5ff' width='98%' align='center'>
  <tr>
   <td>FirstName:</td>
   <td>
    <input name='.setObjection.setPrepared.setFirstName'
        size='20' value='Fred'/>
   </td>
  </tr>
  <tr>
   <td>Lastname:</td>
   <td>
    <input name='.setObjection.setPrepared.setLastname'
        size='20' value='Flintstone'/>
   </td>
  </tr>
...

表單底部的按鈕調用相應的 WebSphere Process Server 生命周期方法。Claim 和 Cancel 按鈕的是否啟用反映人工任務的當前狀態,它們也決定所有 UI 元素是否啟用。在這兩個狀態之間來回切換可以測試基本的客戶機功能。

組合用戶輸入

現在,只需獲取用戶輸入並將其發送回 WebSphere Process Server。完成人工任務的主要方法名為 complete() 或其變體之一。

Complete 按鈕調用 getDataFromForm 方法,它采用相同的模式:調用執行實際工作的 getData,getData 在樹中移動,如果需要,遞歸地調用其本身,見清單 22。

清單 22. 獲取用戶輸入

protected void getDataFromForm( Object dataObject,
  Map<String, String[]> map)
{
  // Traverse through request parameter
  for( String key : map.keySet())
  {
   String mapValue = map.get(key)[0];
   formsBean.getData( dataObject, key, mapValue );
  }
}

private void getData( Object dataObject,
   String key,
   String value )
{
  …
  // Parse the key to get dataObject field
  String[] array = key.split( "\\.");
  for( int index = 0; index < array.length; index++ )
  {
   String element = array[index];
   if( element.startsWith( "set" ) || element.startsWith( "is" ))
   {
    Method[] methods = dataObject.getClass().getMethods();
    for( Method method : methods )
    {
     Class[] parameters = method.getParameterTypes();
     if( parameters.length == 1 && method.getName().equals( element ))
     {
      Class parameter = parameters[0];
      if( parameter == java.lang.Integer.class )
      {
       method.invoke( dataObject, Integer.valueOf( value ));
      }
      else if( parameter == java.lang.Integer.TYPE )
        …
      }
      else if( parameter.isEnum())
      {
       // Create an instance of an enum with 'fromValue' method
       Method enumMethod = parameter.getMethod( "fromValue", String.class );
       Object newEnum = enumMethod.invoke( parameter, value );
       // Call setter to set the new Enum
       method.invoke( dataObject, newEnum );
      }
      else // object is already built, get the pointer and recurse
      {
       byte[] byteArray = element.getBytes();
       byteArray[0] = 'g';
       String methodName = new String( byteArray );
       Method getter = dataObject.getClass().getMethod( methodName,
        new Class[]{} );
       Object newDataObject = getter.invoke( dataObject, new Object[]{} );

       if( parameter != Enum.class ) // Complex types: recurse
       {
        String newArray = "";
        for( int i = 1; i < array.length; i++ )
        {
         newArray += array[i] + ".";
        }
        getData( newDataObject, newArray, value );
       }
      }
      break;
     }
    } // for
   } // if
  } // for
}

發送用戶輸入

數據樹現在反映用戶輸入,可以發送它了,見圖 16。

圖 16. 編組用戶數據

JAXB 也使編組過程更容易了。創建要傳遞給 WebSphere Process Server Web 服務 API 的對象,然後創建一個響應 soapMessage。然後把它傳遞給 編組器。通過 JAX-WS 創建的運行時工件和反射獲取 WSDL 元數據。

然後在 Faces 請求參數映射中移動,找到實際的用戶數據,把每個元素填充到 soapMessage 中,見清單 23。

清單 23. 編組用戶數據

...
CompleteWithOutput completeWithOutput = new CompleteWithOutput();
SOAPFactory soapFactory = SOAPFactory.newInstance();
SOAPElement soapMessage = soapFactory.createElement( operationName +
     "Response", "nsprefix", wsdlNamespace );
SOAPElement topElement = soapFactory.createElement( outputName );
soapMessage.addChildElement( topElement );

// Get http parameters from Faces Context
Map<String, String[]> map = FacesContext.getCurrentInstance().
  getExternalContext().getRequestParameterValuesMap();

getDataFromForm( inputDataObject, map );
marshaller.marshal( inputDataObject, topElement );

completeWithOutput.set_any( soapMessage );
completeWithOutput.setTkiid( tkiid );
CompleteWithOutputResponse response = htmProxy.completeWithOutput
(completeWithOutput);

這就完成了人工任務。

結束語

本文介紹了 JAXB 的基本概念以及它們如何與 WebSphere Process Server Web 服務 API 結合使用。在線路上發送的是 XML 文檔,而在客戶機環境中使用的是 Java 類,JAXB 以簡潔優雅的方式在這兩者之間建立了橋梁。

討論的重點是 JAXB 開發和運行時環境如何簡化 XSD 到 Java 映射過程,以及如何使用運行時特性開發簡單的用戶界面,即通過 JAXB 運行時和 Java 反射在運行時動態地生成用戶界面。

原文:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1004_jenny/1004_jenny.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved