“使用 XML”專欄啟動了一個新的項目,目的是構造一個輕量級的 XML 客戶機,使小型組織(最多 50 人左右)也能與 XML 服務器交換 XML 交易的工具,本文是該項目的第二部分。
您是說 e-business 嗎?
我發現大多數 business-to-business (B2B)電子商務項目都需要這樣一個的簡單客戶機。關於這種需求的詳細討論,請參閱上一部分,“ 一種輕量級的 XML 客戶機”。下面我介紹主要的依據。
在 B2B 項目中,不同的業務伙伴通過更有效地使用技術聯合它們的力量以提高自身的競爭力。這種努力背後的商業理由千差萬別。許多情況下,目標是實現管理操作(與訂貨、交貨、計價和付款有關的所有文書工作)的自動化以降低成本。簡化文書工作可能還有助於降低庫存需要(通過推遲訂貨)或交貨成本(通過供應商直接將貨物發送給用戶或消費者)。
在其他情況中,目標是組合兩個或更多伙伴的服務和產品制造新的服務或產品。 按單加工是一個基本的例子,消費者訂購的貨物在工廠中定制。這種方式最常用於直銷,因為如果控制了銷售渠道和工廠就更容易實行。為了在轉銷渠道中有效地采用按單加工,需要有力的 e-business 解決方案。
盡管商業動機不同,技術實現的核心卻總是業務伙伴間更有效的數據交換。在許多方面,e-commerce 項目類似於企業級應用集成(eAI),但是延伸到了企業的邊界之外(順便說一下,這一點極大地增加了復雜程度,因為要應付不同的企業文化和商業策略)。
e-commerce 項目經常涉及到少量大型企業和許多較小的企業。規模可能相差甚遠,我記得有一個項目,其中的一個伙伴每年要處理上百萬筆交易,而其他伙伴的交易量只有一千到一萬筆。
既然規模不同,顯然需求也不一樣,不幸的是多數 e-commerce 工具都只是針對最大的組織的需要。這些工具被設計成自動處理大量的交易,而實際上最終對多數伙伴而言過於復雜和昂貴。一些項目沒有意識到這種差異,也從來沒有試圖修正(只要試試在僅供家庭使用的機器上部署 J2EE 服務器就知道了)。另一方面,成功的項目則提供了某種輕量級的解決方案,充分考慮到了較小企業的要求。
在 上一篇文章中,我根據自己從事 e-commerce XML 項目的經驗,討論了這種輕量級解決方案的需求。本文中我將討論通信協議,包括 Simple Object Access Protocol (簡單對象訪問協議,SOAP),並創建輕量級客戶機的第一個版本。
與服務器對話
輕量級客戶機最重要的一個功能是能夠用存儲在業務程序中的數據准備 XML 交易。我原來提出的解決方案是使用 XI,以及我以前在該專欄中開發的文本-XML轉換工具(有關以前各部分的鏈接請參閱 參考資料)。
事實上,多數商業程序都能以逗號分隔值(CSV)文件保存數據。通過 XI 所用的正則表達式不難解析這些數據。另外一種流行的格式使用定長字段(也稱為扁平文件)。後一種格式在 IBM iSerIEs(原來的 AS/400)的用戶中特別受歡迎。用正則表達式解析這些文件同樣沒有什麼大不了的。
接下來需要向 XML 服務器發送交易。可以使用不同的方法,如:
通過 Html 表單上傳
使用 SOAP
初看起來,e-mail 似乎是最簡單的方法。它也非常適合於沒有永久性 Internet 連接的組織。但有一個問題,即 e-mail 采用什麼樣的結構:在郵件體還是附件中發送 XML 交易?自動發送郵件還是插入用戶的郵箱中?
還有一種辦法是使用 <input type="file"> Html 標簽,要求用戶手動上載 XML 交易。但實際上許多用戶認為這種辦法不好理解。
照我看來,SOAP 是最好的解決方案。SOAP 是一個簡單的協議,使用 HTTP 或 e-mail 自動發送 XML 交易。SOAP 代替了使用 e-mail 和 Html 表單的方法。
SOAP 基礎及其 Java API
Java Community 至少提出了三種 API 處理 SOAP:
Java API for XML-based RPC (JAX-RPC)
Java API for XML Messaging (JAXM)
SOAP with Attachments API for Java (SAAJ)
為何有三種 API 呢?因為 SOAP 是一個模塊化的規范,可以滿足多種不同的需要。根據目標的不同,開發人員可以使用這三種 API 中的一種。
圖 1 說明了 SOAP 協議棧:
圖 1. SOAP 協議棧
/p>
協議棧的底層是通信協議——主要是 HTTP 和 SMTP,盡管也可以使用其他的協議(如 HMS)。通信協議的上面是可選的文件附件和安全層,用 MIME 實現。這兩層上面的一切都使用 XML 實現。SOAP 消息是單純用於交換 XML 交易的協議。主要有信封和錯誤處理組成。
最後面的但同樣重要的是 RPC 層,規定了如何在 XML 中調編碼方法。這是 SOAP 最知名的特性。
不同的 Java API 針對協議棧中的不同層:
SAAJ 針對附件層
JAXM 針對消息層
JAX-RPC 針對 RPC 層
因為將使用 XI 准備交易,所以不需要 Java 對 RPC 的支持。本項目中將使用 JAXM,可能還有 SAAJ。
觀察 JAXM
如果觀察 JAXM,您會發現它是對 HTTP 庫一個很簡陋的包裝。JAXM 之所以如此簡陋,是因為您主管——或者更明確地說,負責創建 XML 請求和解碼響應(盡管 JAXM 確實會自動標記某些錯誤)。
這種設計與 JAX-RPC 恰好相反,後者向開發人員隱藏了所有的 XML。再說一次,如果不願意看到任何 XML,應該選擇 JAX-RPC。如果希望管理 XML 則應選擇 JAXM,這恰恰我們的目標。
我已經在類 SOAPLink 中增加了 XI 對 JAXM 的支持。本文中不再詳細介紹 SOAPLink ,因為多數代碼都很熟悉。可以從在線資料庫(請參閱 參考資料)下載源代碼。清單1是 JAXM 集成的摘要。
清單 1. JAXM 在 XI 中的集成
public synchronized File apply(File source,boolean overwrite)
throws exception, SAXException,
TransformerException, SOAPException
{
File requestFile = requestLink.applyTo(source,overwrite);
SOAPMessage request = messageFactory.createMessage(),
response = null;
SOAPPart part = request.getSOAPPart();
InputStream is = new FileInputStream(requestFile);
try
{
part.setContent(new StreamSource(is));
SOAPConnection connection =
connectionFactory.createConnection();
try
{
response = connection.call(request,endpoint);
}
finally
{
connection.close();
}
}
finally
{
is.close();
}
if(response == null)
return null;
String fname = requestFile.getName();
int pos = fname.lastIndexOf('.');
String base = pos != -1 ? fname.substring(0,pos + 1)
: fname + '.';
File responseFile = new File(output,base + "soap");
int index = 1;
while(responseFile.exists() && !overwrite)
{
responseFile = new File(output,base + index + ".soap");
index++;
}
OutputStream os = new FileOutputStream(responseFile);
try
{
response.writeTo(os);
}
finally
{
os.close();
}
return responseLink.applyTo(responseFile,overwrite);
}
創建 SOAP 消息只需要實例化 SOAPMessage 對象。 SOAPMessage 允許訪問消息中的所有元素: SOAP 信封、附件、MIME 頭等等。
從 SOAPMessage 可以檢索 SOAPPart 對象,它包括 SOAP 請求本身(不含附件)。初始化 SOAPPart 最簡單的方式是調用 setContent() 方法並傳遞一個 XML 文檔。
發送文檔則使用 SOAPConnection 對象的 call() 方法。 call() 方法返回一個 SOAPMessage 對象,包含服務器的響應。
解碼 清單 1中的結果,一種辦法是用 writeTo() 方法將其保存到文件中然後再解析結果。注意這樣做是不安全的,因為如果服務器用附件響應,解析就會失敗。
這就突出了 JAXM 的一個問題:JAXM 與其他 XML API 的交互不容易。比如,Java 語言通常使用 DOM API 表示 XML 樹。JAXM 定義了一組不同的對象(如 SOAPElement ),基本上是 DOM 的復制品。我沒有發現 JAXM 從這裡獲得了什麼好處,因為它使得用類似的 Java XML 庫處理 JAXM 消息非常困難。
但目前我仍然堅持使用這種方案,因為它足以處理簡單的請求。我計劃在下篇文章中進一步研究這個問題,試圖提出一種更好的解決方案。如果您有什麼建議,我非常願意聽一聽。(您也可以單擊本文頂部或底部的 討論來訪問論壇。)我正在考慮的一種辦法是 DOM-to-JAXM (或 SAX-to-JAXM) 轉換庫。
我使用 XSLTLink 創建交易並處理響應。一般讀者應該熟悉 XSLTLink :它是 XI 的一個主要入口。
消息和樣式表
現在我開始討論 SOAP 交易本身和管理它的 XSLT 樣式表。
概言之,關於 SOAP 消息最重要的一點就是:
SOAP 消息是一個 XML 文檔。
整個交易包括在一個 Envelope 元素中。
信封包含可選的 Header 和必需的 Body 。
Body 包含交易的主要部分,可以是 RPC 調用或其他任何 XML 文檔。
Header 包含了對處理調用非常有用的附加信息,如安全、驗證、會話信息或者其他任何有用的信息。
如果不能識別,服務器可以忽略全部或部分 Header(比如,無狀態的服務器可能會忽略 Header 中的會話信息)。
忽略 Header 並非總是合理的(比如可能不希望處理不帶安全信息的請求)。那些不允許忽略的頭元素必須用 mustUnderstand="1" 屬性標記。
可以用 encodingStyle 屬性指定如何創建頭元素。
如果出現錯誤,Body 中將包含 fault 元素。
服務器用包含相同的信封、Header 和 Body 的另一個消息應答。
為了說明如何用 XSLT 創建 SOAP 請求,我使用了 Apache 的 AXIS 1.1 版工具包(最初是一個 IBM 項目,後來捐獻給 apache Foundation)。AXIS 主要是為 JAX-RPC 設計的,到目前為止只實現了 JAXM 的一個子集。
注意,AXIS 1.1 版實現的是 SOAP 1.1,而不是 W3C 最近批准的修訂版。
AXIS 帶有許多例子。為了測試這個輕量級客戶機,我決定重新實現計算器服務。計算器服務是 AXIS 用戶指南中的第二個例子,因此並不很難。該服務接受兩個數字進行加減運算。樣式表接受只有兩行的文本文件,如清單 2 所示:
清單 2. 文本文件中的請求參數
+
10,4
第一行包括 + 號或 - 號。第二行包括用逗號分開的兩個數。
文本文件包含了請求參數,但還不是 SOAP 編碼。SOAP 編碼由清單 3 中的樣式表完成,它只是像前面所述的那樣創建信封、體和體請求。
清單 3. 准備 SOAP 交易
<?XML version="1.0"?>
<xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
XMLns:xi="http://ananas.org/2002/xi/rules"
xmlns:soapenv="http://schemas.XMLsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
XMLns:p="http://ananas.org/2003/xi/params"
version="1.0">
<xi:rules version="1.0"
defaultPrefix="p"
targetNamespace="http://ananas.org/2003/xi/params">
<xi:ruleset name="data">
<xi:match name="plus" pattern="^+$"/>
<xi:match name="minus" pattern="^-$"/>
<xi:match name="line">
<xi:filler pattern="^"/>
<xi:group pattern=".*" name="op1"/>
<xi:filler pattern=","/>
<xi:group pattern=".*" name="op2"/>
<xi:filler pattern="$"/>
</xi:match>
<xi:error message="could not build request, please check file"/>
</xi:ruleset>
</xi:rules>
<xsl:template match="/">
<soapenv:Envelope>
<soapenv:Body>
<xsl:apply-templates/>
</soapenv:Body>
</soapenv:Envelope>
</xsl:template>
<xsl:template match="p:data">
<add soapenv:encodingStyle="http://schemas.XMLsoap.org/soap/encoding/">
<op1 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op1"/></op1>
<op2 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op2"/></op2>
</add>
</xsl:template>
<xsl:template match="p:data[p:minus]">
<subtract soapenv:encodingStyle="http://schemas.XMLsoap.org/soap/encoding/">
<op1 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op1"/></op1>
<op2 xsi:type="xsd:int"><xsl:value-of select="p:line/p:op2"/></op2>
</subtract>
</xsl:template>
</xsl:stylesheet>