當我們的 Web Service 有傳遞檔案的需求時,除了使用 byte 陣列之外,還可以使用 javax.activation.DataHandler 這個類別。

要使用這個類別,尚需引入 activation.jar (JAF) 與 mail.jar 這兩個套件。

這裏要注意的是,有些教學網站只教你去 SUN 網站下載 JAF 來使用即可,但其實 JAF 就是我們使用 java mail 時需要引入的 activation.jar 檔,不過這也還好,主要是這些網站並沒說要引入 mail.jar,結果就是在執行時會丟出『java.lang.RuntimeException: No support for attachments』的錯誤,當時的確是造成了笨鳥的困擾,好加在有在別的網站找到原因,不然又不知要被困多久了 >_<

言歸正傳,由於 javax.activation.DataHandler 類別並不是 Web Service 傳遞協定的標準,因此如果我們像前一篇「利用 Eclipse 簡單建立 AXIS 的 WebService」裏寫的那樣直接用 Eclipse 來幫我們產生並註冊 Web Service 的話,在過程中很有可能會有錯誤的產生(IWAB0399E Error in generating Java from WSDL:  java.io.IOException: Type {http://activation.javax}DataHandler is referenced but not defined.
) 而導致無法完成部署,所以我們必需自行建置/修改部署檔的內容,並手動進行部署,至於如何手動部署已在前一篇講解過了,此處就不再解講。

首先,我們先寫一個傳送/接收 DataHandler 的 Web Service

public class DataHandlerWS1 {
 /**
 * 接收一個 Datahandler,並將其中夾帶的檔案存到磁碟
 */
 public void receive(DataHandler dh, String name) {
 try {
 copyStream(dh, name);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 /**
 * 讀取磁碟中的一個檔案回傳
 */
 public DataHandler send() {
 DataHandler dh = null;
 try {
 dh = new DataHandler((new File("E:\\\\MSN\\onion14.gif")).toURL());
 } catch (Exception e) {
 e.printStackTrace();
 }
 return dh;
 }

 /**
 * 將 DataHandler 寫出成檔案,此方法不發佈成 Web Service。
 */
 private void copyStream(DataHandler dh, String name) throws Exception {
 BufferedInputStream bis = null;
 BufferedOutputStream bos = null;

 try {
 bis = new BufferedInputStream(dh.getInputStream());
 bos = new BufferedOutputStream(
 new FileOutputStream(new File("C:\\tmp\\axis\\" + name)));

 byte[] buffer = new byte[1024 * 4];
 int n = 0;

 while ((n = bis.read(buffer)) != -1) {
 bos.write(buffer, 0, n);
 }
 } finally {
 if (bis != null) {
 bis.close();
 }

 if (bos != null) {
 bos.close();
 }
 }
 }
}

上面的 Web Service 只是一個很簡單的 Java 程式,receive() 接收由呼叫端傳來的 DataHandler 物件,並將其依傳入的檔名存入磁碟中;而 send() 則是從磁碟中取出一個檔案包在 DataHandler 物件後,傳回給呼叫端。

接著撰寫部署用的 wsdd 檔。
wsdd 檔有兩種寫法,一種是特別去宣告 DataHandler 這個類別,並指定為其序列與反序列化的類別;而另一種則是將 DataHandler 以標準的 base64Binary 來替代。以下會列出這兩種寫法。

.特別宣告 DataHandler 的寫法
   宣告是為了讓 Server 在傳出 / 收到 DataHandler 類別時,知道該如何去序列與反序列化。

 <?xml version="1.0" encoding="UTF-8"?>
 <deployment xmlns = "http://xml.apache.org/axis/wsdd/" 
 xmlns:java = "http://xml.apache.org/axis/wsdd/providers/java"
 xmlns:ns1 = "http://axis.teach"
 xmlns:tns = "http://www.w3.org/2001/XMLSchema"
 xmlns:dh = "http://activation.javax">
 <!-- 定義 Web Service -->
 <service name="DataHandlerWS1" provider="java:RPC" style="wrapped" use="literal">
 <!-- 此 Web Service 指向的類別全名-->
 <parameter name="className" value="teach.axis.DataHandlerWS1"/>
 <!-- 允許開放呼叫的方法名,有多個用空格隔開,若要全開放則填上 * -->
 <parameter name="allowedMethods" value="send receive"/>
 <!-- 宣告方法:send 方法有回傳值,此處亦指定其回傳型態-->
 <operation name="send" qname="ns1:send" 
 returnQName="ns1:sendReturn" 
 returnType="dh:DataHandler">
 </operation>
 <!-- 宣告方法:receive 方法有兩個傳入值,此處會去宣告此兩個參數值的型態-->
 <operation name="receive" qname="ns1:receive">
 <parameter name="dh" qname="ns1:dh" type="dh:DataHandler"/>
 <parameter name="name" qname="ns1:name" type="tns:string"/>
 </operation>
 <!-- 宣告 DataHandler 這個類別,包括指向的類別與序列/反序列化的類別等-->
 <typeMapping qname="dh:DataHandler" 
 languageSpecificType="java:javax.activation.DataHandler" 
 serializer="org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory" 
 deserializer="org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory" 
 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
 </service>
 </deployment>

上面的 wsdd 檔中,24 28 行是宣告 javax.activation.DataHandler 這個類別型態,而 16 與 20 行則是使用了宣告的 QName 來指名回傳與接收的是 DataHandler 物件。

.使用 base64Binary 的寫法

  <?xml version="1.0" encoding="UTF-8"?>
 <deployment xmlns = "http://xml.apache.org/axis/wsdd/" 
 xmlns:java = "http://xml.apache.org/axis/wsdd/providers/java"
 xmlns:ns1 = "http://axis.teach"
 xmlns:tns = "http://www.w3.org/2001/XMLSchema"
 xmlns:dh = "http://activation.javax">
 <!-- 定義 Web Service -->
 <service name="DataHandlerWS1_DP2" provider="java:RPC" style="wrapped" use="literal">
 <!-- 此 Web Service 指向的類別全名-->
 <parameter name="className" value="teach.axis.DataHandlerWS1"/>
 <!-- 允許開放呼叫的方法名,有多個用空格隔開,若要全開放則填上 * -->
 <parameter name="allowedMethods" value="send receive"/>
 <!-- 宣告方法:send 方法有回傳值,此處亦指定其回傳型態-->
 <operation name="send" qname="ns1:send" 
 returnQName="ns1:sendReturn" 
 returnType="tns:base64Binary">
 </operation>
 <!-- 宣告方法:receive 方法有兩個傳入值,此處會去宣告此兩個參數值的型態-->
 <operation name="receive" qname="ns1:receive">
 <parameter name="dh" qname="ns1:dh" type="tns:base64Binary"/>
 <parameter name="name" qname="ns1:name" type="tns:string"/>
 </operation>
 </service>
 </deployment>

請看第 16 與 20 行,跟前一個 wsdd 檔比較,原本傳入與傳出的是 DataHandler 類別,但此處我們使用標準的 base64Binary 來替代。


部署用的 wsdd 檔完成後,依序執行上面兩個 wsdd 進行部署 (執行 wsdd 的步驟請參考上一篇文章),若部署成功則現在 Server 上會有 DataHandlerWS1 (使用宣告 DataHandler 的方式) 與 DataHandlerWS1_DP2 (使用 base64Binary) 兩支 Web Service。

部署完成後就可以寫 Client 端程式來試試看了。

package teach.axis.client;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;

import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.encoding.XMLType;

import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory;
import org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory;

public class DataHandlerWS1Client {
 public static final String url = "http://localhost:8080/AxisTeach/services/DataHandlerWS1";
 public static final QName dhQName = new QName("http://activation.javax", "DataHandler");
 /**
 * 建立呼叫 Web Service 的 Call 物件 
 */
 private static Call createCall() throws ServiceException {
 Service service = new Service();
 Call call = (Call) service.createCall();

 call.setTargetEndpointAddress(url);
 // 由於使用了非 WebService 標準的 DataHandler,因此要註冊。
 call.registerTypeMapping(
 DataHandler.class, 
 dhQName, 
 JAFDataHandlerSerializerFactory.class, 
 JAFDataHandlerDeserializerFactory.class);

 return call;
 }
 /**
 * 測試 DataHandlerWS1 的 receive() 方法
 */
 public static void callReceive(Call call) throws Exception {
 // 清空前一次 Call 的呼叫所設定的參數
 call.clearOperation();
 // 傳入參數也可寫成 new QName("receive")
 call.setOperationName(new QName(url, "receive"));
 // 設定傳入參數的型態(QName)
 call.addParameter("dh", dhQName, ParameterMode.IN); 
 call.addParameter("name", XMLType.XSD_STRING, ParameterMode.IN); 
 
 DataHandler dh = new DataHandler((new File("E:\\\\MSN\\em184.gif")).toURL());
 call.invoke(new Object[]{dh, "toWS.gif"}); 
 }

 /**
 * 測試 DataHandlerWS1 的 send() 方法
 */
 public static void callSend(Call call) throws Exception {
 // 清空前一次 Call 的呼叫所設定的參數
 call.clearOperation();
 // 傳入參數也可寫成 new QName("send")
 call.setOperationName(new QName(url, "send"));
 // 宣告回傳的類型
 call.setReturnClass(DataHandler.class);

 DataHandler dh = (DataHandler) call.invoke((Object[]) null);
 copyStream(dh, "fromWS.gif");
 }

 /**
 * 將 DataHandler 內的 input stream 存成檔案。
 */
 private static void copyStream(DataHandler dh, String name) throws Exception {
 BufferedInputStream bis = null;
 BufferedOutputStream bos = null;

 try {
 bis = new BufferedInputStream(dh.getInputStream());
 bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\tmp\\axis\\" + name)));

 byte[] buffer = new byte[1024 * 4];
 int n = 0;

 while ((n = bis.read(buffer)) != -1) {
 bos.write(buffer, 0, n);
 }

 } finally {
 if (bis != null) {
 bis.close();
 }
 if (bos != null) {
 bos.close();
 }
 }
 }

 public static void main(String[] args) throws Exception{
 Call call = createCall();
 callReceive(call);
 callSend(call);
 }

}

請注意上面 Client 程式中的第 31 35 行,在建出 Client 物件之後,要去註冊 DataHandler 類別與指定為該類別序列化與反序列化的類別。也許大家會覺得奇怪的是如果剛剛使用宣告 DataHandler 的方式來編寫 wsdd 檔的話,那時不是已經宣告了嗎?為什麼這裏還要宣告一次?這是因為 wsdd 檔是給 Server 看的 Client 並不知道,因此當 Client 在遇到非 Web Service 標準的 DataHandler 類別就會不知該怎麼去序列化與反序列化它了,所以在編寫 Client 端的呼叫程式時,仍然需要進行宣告/註冊 DataHandler 類別的步驟。

當這支 Client 程式執行成功後,將 url 改成呼叫用 base64Binary 取代宣告 DataHandler 的 wsdd 檔所部署的 Web Service (DataHandlerWS1_DP2),程式其他的部份不需要去動,執行後發現也能成功。這裏可能有人又有疑問了,那就是不是用 base64Binary 來取代 DataHandler 了嗎?為什麼還是要註冊 DataHandler 類別,而且傳送與接收 DataHandler 類別都沒問題呢?其實 base64Binary 只是告訴 Web Service 在傳送過程中使用這個型態,當接收端(Server 或 Client) 在收到資料時,只要能依你註刪的方式反序列回來,一樣是能轉成我們要的類別的。這部份只光用這樣描述似乎不大能理解,下一篇再來用實際的例子說明只要能反序列化出來,Server 與 Client 兩端傳遞的類別是可以不同的。

 

參考文件:
.用DataHandler来实现一个带附件的soap请求的web services
.第 9 章 使用 DataHandler 实现文件上传与下载
.用基于 AXIS 的 WebService 传输文件
.利用 AXIS 开发 WebService (五) --- 如何传递文件
.利用 axis 实现文件传递的问题 -- (問題解決)

文章標籤
全站熱搜
創作者介紹
創作者 大笨鳥 的頭像
大笨鳥

大笨鳥的私房菜

大笨鳥 發表在 痞客邦 留言(0) 人氣(5,738)