當我們的 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 实现文件传递的问题 -- (問題解決)

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大笨鳥 的頭像
    大笨鳥

    大笨鳥的私房菜

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