一、SOAPIn Axis2

在前两天的教程中,我们学习到了用Axis2如何进行复杂数据、简单数据进行传输。

正如我在前一天教程中所说,在web service的世界里,一切都是基于SOAP的,因此在今天我们将学习Axis2中的SOAP特性。

今天的课程将用3个例子来完成即:

1)  客户端与服务端使用SOAP进行通讯

2)  服务端将Exception以SOAPFault的形式抛给客户端

3)  使用SWA(Soap With Attachment)来进行附件传送


二、客户端与服务端使用SOAP进行通讯

来看下面这个Web Service:

通向架构师的道路(十一)--之Axis2 Web Service(三) _import


下面是Service端的源码

org.sky.axis2.soap.SoapService

package org.sky.axis2.soap;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import java.util.*;


public class SoapService {


public static OMElement requestSoap = null;


public OMElement request(OMElement soapBody) {

  requestSoap = soapBody;


  Iterator it = requestSoap.getChildElements();

  OMElement issuerElement = (OMElement) it.next();

  OMElement serialElement = (OMElement) it.next();

  OMElement revocationDateElement = (OMElement) it.next();


  String issuer = issuerElement.getText();

  String serial = serialElement.getText();

  String revocationDate = revocationDateElement.getText();

  System.out.println("issuer=====" + issuer);

  System.out.println("serial=====" + serial);

  System.out.println("revocationDate=====" + revocationDate);

  OMFactory soapFactory = OMAbstractFactory.getOMFactory();

  OMNamespace omNs = soapFactory.createOMNamespace(

"http://soap.axis2.sky.org", "");

  OMElement soapResponse = soapFactory.createOMElement("SoapResponse",

omNs);


OMElement soapIssuer = soapFactory.createOMElement("Issuer", omNs);

                  soapIssuer.setText("issuer: " + issuer);

                  soapResponse.addChild(soapIssuer);


                  OMElement soapSerial = soapFactory.createOMElement("Serial", omNs);

                  soapSerial.setText("serial: " + serial);

                  soapResponse.addChild(soapSerial);


                  OMElement soapRevokeDate = soapFactory.createOMElement("RevokeDate",

                                    omNs);

                  soapRevokeDate.setText("RevocationDate: " + revocationDate);

                  soapResponse.addChild(soapRevokeDate);

soapResponse.build();


  return soapResponse;

}


}

来看它的service.xml的描述

通向架构师的道路(十一)--之Axis2 Web Service(三) _import_02


<service name="SoapService">

<description>

  This is the service for revoking certificate.

</description>

<parameter name="ServiceClass" locked="false">

  org.sky.axis2.soap.SoapService

</parameter>

<operation name="request">

  <messageReceiver

class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />

  <actionMapping>urn:request</actionMapping>

</operation>

</service>


该Web Service接受一个Soap请求,该请求为如下格式:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.axis2.sky.org">

  <soapenv:Header/>

  <soapenv:Body>

 <soap:request>

  <soap:request>?</soap:request>

 </soap:request>

  </soapenv:Body>

</soapenv:Envelope>

其中<soap:request></soap:request>中间的内容,应该如下所示:

<Request xmlns="http://10.225.104.122">

<Issuer>1234567890</Issuer>

<Serial>11111111</Serial>

<RevokeDate>2007-01-01</RevokeDate>

</ Response >

我们假设它是一个购买图书的定单,服务端收到这个请求后会返回一个定单信息给调用它的客户端,服务端将返回如下内容(此处不做任何业务处理,只是很简单的传值回客户端)。

<SoapResponse xmlns="http://soap.axis2.sky.org">

<Issuer>issuer: Wrox</Issuer>

<Serial>serial: 1111111111ISBN</Serial>

<RevokeDate>RevocationDate: 2012-07-29</RevokeDate>

</SoapResponse>

为生成上述这个SoapResponse我们在Service端的核心代码如上面加粗部分的代码所示,由其注意这个“soapResponse.build();”。

下面我们来看这个客户端是怎么写的,我们这边用的是非阻塞式客户端

org.sky.axis2.soap.SoapServiceClient

package org.sky.axis2.soap;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axis2.AxisFault;

import org.apache.axis2.Constants;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;

import org.apache.axis2.client.async.AxisCallback;

import org.apache.axis2.context.MessageContext;

import javax.xml.namespace.QName;


public class SoapServiceClient {

private static EndpointReference targetEPR = new EndpointReference(

"http://localhost:8080/Axis2Service/services/SoapService");

public static boolean finish = false;

public static void orderRequest() {

  OMFactory factory = OMAbstractFactory.getOMFactory();

OMNamespace omNs = factory.createOMNamespace(

"http://soap.axis2.sky.org", "");

OMElement issuer = factory.createOMElement("Issuer", omNs);

OMElement serial = factory.createOMElement("Serial", omNs);

OMElement revocationDate = factory.createOMElement("RevocationDate",

omNs);

issuer.setText("Wrox");

serial.setText("1111111111ISBN");

revocationDate.setText("2012-07-29");

OMElement requestSoapMessage = factory.createOMElement("request", omNs);

requestSoapMessage.addChild(issuer);

requestSoapMessage.addChild(serial);

requestSoapMessage.addChild(revocationDate);

requestSoapMessage.build();

  Options options = new Options();

  options.setTo(targetEPR);

  ServiceClient sender = null;

  try {

AxisCallback callback = new AxisCallback() {

public void onMessage(MessageContext msgContext) {

OMElement result = msgContext.getEnvelope().getBody()

.getFirstElement();

  // System.out.println(msgContext.toString());

  // System.out.println(msgContext.getEnvelope().toString());

System.out.println(msgContext.getEnvelope().getBody()

.getFirstElement());

finish = true;

}

public void onFault(MessageContext msgContext) {

  System.out.println(msgContext.getEnvelope().getBody()

.getFault().toString());

}

public void onError(Exception e) {

}

public void onComplete() {

  System.out.println("Completed!!!");

}

};

sender = new ServiceClient();

sender.setOptions(options);

System.out.println("-------Invoke the service---------");

sender.sendReceiveNonBlocking(requestSoapMessage, callback);

synchronized (callback) {

if (!finish) {

  try {

callback.wait(1000);

  } catch (Exception e) {

  }

}

if (!finish) {

  throw new AxisFault(

"Server was shutdown as the async response take too long to complete");

}

}

  } catch (AxisFault e) {

e.printStackTrace();

  } finally {

if (sender != null)

try {

  sender.cleanup();

} catch (Exception e) {

}

  }


}

public static void main(String[] args) {

  orderRequest();

}

}

上述代码和前两天的客户端代码没啥区别,我已经把核心代码用红色给标粗了。

运行后行得到输出

客户端运行后的输出:

通向架构师的道路(十一)--之Axis2 Web Service(三) _import_03


服务端的输出:

通向架构师的道路(十一)--之Axis2 Web Service(三) _import_04


三、服务端将Exception以SOAPFault的形式抛给客户端


上面这个例子很简单,它展示了一个客户端向服务端发送一个request,服务端接收到客户端的Request(OMElement类型)后解析并根据相应的业务逻辑向客户端再返回一个response(OMElement类型)的完整过程。

下面我们要来看的是,如果客户端在调用服务器时发生任何错误,服务端如何把这个错误经过包装后再返回给客户端的例子。

还记得我们在非阻塞式客户端中有如下这样的触发器吗?

public void onMessage(MessageContext msgContext) {

}

public void onFault(MessageContext msgContext) {

}

public void onError(Exception e) {

}

public void onComplete() {

}

此处的onFault就是用于接受从服务端抛过来的Exception的,我们把它称为SOAPFault。

下面来看一个例子,先来看Service端

org.sky.axis2.soap.SoapFaultService

package org.sky.axis2.soap;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axiom.soap.SOAPFactory;

import org.apache.axiom.soap.SOAPFault;

import org.apache.axiom.soap.SOAPFaultCode;

import org.apache.axiom.soap.SOAPFaultReason;

import org.apache.axis2.AxisFault;

import org.apache.axis2.context.MessageContext;

public class SoapFaultService {

private int i = 0;

public OMElement getPrice(OMElement request) throws AxisFault {

  if (request == null) {

SOAPFault fault = getSOAPFault();

return fault;

  }

  OMFactory factory = OMAbstractFactory.getOMFactory();

  OMNamespace ns = factory.createOMNamespace("", "");

  OMElement response = factory.createOMElement("Price", ns);

  response.setText(String.valueOf(i++));

  return response;

}

private SOAPFault getSOAPFault() {

  MessageContext context = MessageContext.getCurrentMessageContext();

  SOAPFactory factory = null;

  if (context.isSOAP11()) {

factory = OMAbstractFactory.getSOAP11Factory();

  } else {

factory = OMAbstractFactory.getSOAP12Factory();

  }

SOAPFault fault = factory.createSOAPFault();

SOAPFaultCode faultCode = factory.createSOAPFaultCode(fault);

                  faultCode.setText("13");

                  factory.createSOAPFaultValue(faultCode);

                  SOAPFaultReason faultReason = factory.createSOAPFaultReason(fault);

                  faultReason.setText("request can not be null");

                  factory.createSOAPFaultText(faultReason);

                  factory.createSOAPFaultDetail(fault);

  return fault;

}

}

注意加粗部分的代码,由其是标成红色的代码为核心代码。

来看Service描述:

<service name="SoapFaultService">

<Description>

  Please Type your service description here

</Description>

<parameter name="ServiceClass" locked="false">org.sky.axis2.soap.SoapFaultService

</parameter>

<operation name="getPrice">

  <messageReceiver

class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />

  <actionMapping>urn:getPrice</actionMapping>

</operation>

</service>

上述这个WebService接受一个输入的参数,如果输入的内容为空,则返回一个SoapFault,即键值为13,内容为” request can not be null”。

我们来看客户端的代码

org.sky.axis2.soap.SoapFaultClient

package org.sky.axis2.soap;

import java.util.Iterator;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axis2.AxisFault;

import org.apache.axis2.Constants;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;

import org.apache.axis2.client.async.AxisCallback;

import org.apache.axis2.context.MessageContext;


public class SoapFaultClient {

static boolean finish = false;

public static void main(String[] args) {

  EndpointReference epr = new EndpointReference(

  "http://localhost:8080/Axis2Service/services/SoapFaultService");

  ServiceClient sender = null;

  try {

OMFactory factory = OMAbstractFactory.getOMFactory();

OMNamespace ns = factory.createOMNamespace(

  "http://soap.axis2.sky.org", "");

OMElement request = factory.createOMElement("Price", ns);

Options options = new Options();

options.setAction("urn:getPrice");

options.setTo(epr);

options.setTransportInProtocol(Constants.TRANSPORT_HTTP);

options.setUseSeparateListener(true);

AxisCallback callback = new AxisCallback() {

public void onMessage(MessageContext msgContext) {

  OMElement result = msgContext.getEnvelope().getBody()

.getFirstElement();

  OMElement priceElement = result;

  System.out.println("price====" + priceElement.getText());

  finish = true;

}

public void onFault(MessageContext msgContext) {

QName errorCode = new QName("faultcode");

QName reason = new QName("faultstring");

// System.out.println("on

// fault:"+msgContext.getEnvelope().getBody().getFault().toString());

OMElement fault = msgContext.getEnvelope().getBody()

.getFault();

System.out.println("ErrorCode["

+ fault.getFirstChildWithName(errorCode).getText()

+ "] caused by: "

+ fault.getFirstChildWithName(reason).getText());

}


public void onError(Exception e) {

}


public void onComplete() {

  System.out.println("OnComplete!!!");

}

};

sender = new ServiceClient();

sender.setOptions(options);

sender.engageModule("addressing");

try {


// sender.sendReceiveNonBlocking(request, callback);

sender.sendReceiveNonBlocking(null, callback);

} catch (AxisFault e) {

System.out.println("Exception occur!");

System.out.println(e.getMessage());

}

synchronized (callback) {

if (!finish) {

  try {

callback.wait(1000);

  } catch (Exception e) {

  }

}

}

  } catch (AxisFault e) {

e.printStackTrace();

System.out.println(e.getMessage());

  } finally {


try {

sender.cleanup();

} catch (Exception e) {

}

  }


}

}

注意红色并加粗部分的代码,为了抓到服务端抛过来的SoapFault我们必须使用非阻塞式,因此我们在onFault处,进行接受服务端错误的处理。

注意:

我们调用Service端时没有传入Service端所需要的request的参数:

// sender.sendReceiveNonBlocking(request,callback);

sender.sendReceiveNonBlocking(null,callback);

这将构成Service端抛出SoapFault。

来看运行效果:

通向架构师的道路(十一)--之Axis2 Web Service(三) _客户端_05


四、使用SWA(Soap WithAttachment)来进行附件传送


有了上面两个例子的基础后,我们将使用这个例子来结束Axis2中的Soap特性的教学。

在Axis2中传输附件有两种形式,一种叫MTOM,一种就是SWA。

SWAP即Soap With Attachment,这是业界的标准。

所谓的SWA传输,即客户端把需要上传的文件,编译成两进制代码凌晨随着soap的request一起推送到服务端,该两进制代码以AttachmentId的形式来表示,即如下这样的一个soap body:

<soapenv:Body>

<uploadFile xmlns="http://p_w_upload.axis2.sky.org">

<name>test.jpg</name>

<attchmentID>urn:uuid:8B43A26FEE1492F85A1343628038693</attchmentID>

</uploadFile>

</soapenv:Body>

服务端收到该soap的request可以直接使用如下的语句将这个AttachmentId还原成输出流:

DataHandler dataHandler = p_w_upload.getDataHandler(attchmentID);

File file = new File(uploadFilePath.toString());

fileOutputStream = new FileOutputStream(file);

dataHandler.writeTo(fileOutputStream);

fileOutputStream.flush();

在我们这个例子内,我们将使用客户端上传一个jpg文件,服务端收到该jpg文件(可以是任何的两进制文件)后解析后存入服务端的一个目录。

先来看服务端代码

org.sky.axis2.p_w_upload.FileUploadService

package org.sky.axis2.p_w_upload;


import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;


import javax.activation.DataHandler;


import org.apache.axiom.p_w_uploads.Attachments;

import org.apache.axis2.context.MessageContext;

import org.sky.axis2.util.UUID;


public class FileUploadService {

public String uploadFile(String name, String attchmentID) throws Exception {

  FileOutputStream fileOutputStream = null;

  StringBuffer uploadFilePath = new StringBuffer();

  String fileNamePrefix = "";

  String fileName = "";

  try {

MessageContext msgCtx = MessageContext.getCurrentMessageContext();

Attachments p_w_upload = msgCtx.getAttachmentMap();

DataHandler dataHandler = p_w_upload.getDataHandler(attchmentID);

fileNamePrefix = name.substring(name.indexOf("."), name.length());

fileName = UUID.getUUID();

System.out.println("fileName=====" + fileName);

System.out.println("fileNamePrefix====" + fileNamePrefix);

uploadFilePath.append("D:/upload/axis2/");

uploadFilePath.append(fileName);

uploadFilePath.append(fileNamePrefix);

System.out

.println("uploadFilePath====" + uploadFilePath.toString());

File file = new File(uploadFilePath.toString());

fileOutputStream = new FileOutputStream(file);

dataHandler.writeTo(fileOutputStream);

fileOutputStream.flush();

  } catch (Exception e) {

throw new Exception(e);

  } finally {

try {

if (fileOutputStream != null) {

  fileOutputStream.close();

  fileOutputStream = null;

}

} catch (Exception e) {

}

  }

  return "File saved succesfully.";

}

}

下面是服务端的描述

通向架构师的道路(十一)--之Axis2 Web Service(三) _service_06


service.xml文件的内容为:

<service name="AttachmentService">

<parameter name="ServiceClass">org.sky.axis2.p_w_upload.FileUploadService

</parameter>

<operation name="uploadFile">

  <actionMapping>urn:uploadFile</actionMapping>

  <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />

</operation>

</service>

该服务端接受客户端上传的附件后使用UUID重新命名上传的文件名,并将其存入服务端的” D:/upload/axis2/”目录中。

来看客户端代码

org.sky.axis2.p_w_upload.FileUploadClient

package org.sky.axis2.p_w_upload;


import java.io.File;


import javax.activation.DataHandler;

import javax.activation.FileDataSource;

import javax.xml.namespace.QName;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMNamespace;

import org.apache.axiom.soap.SOAP11Constants;

import org.apache.axiom.soap.SOAPBody;

import org.apache.axiom.soap.SOAPEnvelope;

import org.apache.axiom.soap.SOAPFactory;

import org.apache.axis2.Constants;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.client.OperationClient;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;

import org.apache.axis2.context.ConfigurationContext;

import org.apache.axis2.context.ConfigurationContextFactory;

import org.apache.axis2.context.MessageContext;

import org.apache.axis2.wsdl.WSDLConstants;


public class FileUploadClient {

private static EndpointReference targetEPR = new EndpointReference(

"http://localhost:8080/Axis2Service/services/AttachmentService");


public static void main(String[] args) throws Exception {

  new FileUploadClient().transferFile();

}


public void transferFile() throws Exception {

  String filePath = "D:/deployment/test.jpg";

  String destFile = "test.jpg";

  Options options = new Options();

  options.setTo(targetEPR);

options.setProperty(Constants.Configuration.ENABLE_SWA,

Constants.VALUE_TRUE);

options.setSoapVersionURI(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);

  options.setTimeOutInMilliSeconds(10000);

  options.setTo(targetEPR);

  options.setAction("urn:uploadFile");

ConfigurationContext configContext = ConfigurationContextFactory

.createConfigurationContextFromFileSystem(

"D:/wspace/Axis2Service/WebContent/WEB-INF/modules",

null);


  ServiceClient sender = new ServiceClient(configContext, null);

  sender.setOptions(options);

OperationClient mepClient = sender

.createClient(ServiceClient.ANON_OUT_IN_OP);

MessageContext mc = new MessageContext();

FileDataSource fileDataSource = new FileDataSource(new File(filePath));


  // Create a dataHandler using the fileDataSource. Any implementation of

  // javax.activation.DataSource interface can fit here.

DataHandler dataHandler = new DataHandler(fileDataSource);

String p_w_uploadID = mc.addAttachment(dataHandler);


  SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();

  SOAPEnvelope env = fac.getDefaultEnvelope();

  OMNamespace omNs = fac.createOMNamespace(

"http://p_w_upload.axis2.sky.org", "");

  OMElement uploadFile = fac.createOMElement("uploadFile", omNs);

  OMElement nameEle = fac.createOMElement("name", omNs);

  nameEle.setText(destFile);

OMElement idEle = fac.createOMElement("attchmentID", omNs);

idEle.setText(p_w_uploadID);

uploadFile.addChild(nameEle);

uploadFile.addChild(idEle);

env.getBody().addChild(uploadFile);

  System.out.println("message====" + env);

  mc.setEnvelope(env);


  mepClient.addMessageContext(mc);

  mepClient.execute(true);

  MessageContext response = mepClient

.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);

  SOAPBody body = response.getEnvelope().getBody();

  OMElement element = body.getFirstElement().getFirstChildWithName(

new QName("http://p_w_upload.axis2.sky.org", "return"));

  System.out.println(element.getText());

}

}

注意红色加粗部分的代码,由其是:

FileDataSource fileDataSource = new FileDataSource(new File(filePath));

String p_w_uploadID = mc.addAttachment(dataHandler);

这两句就是把客户端需要上传的附件转成AttachmentId的语句,然后把这个AttachementId作为一个OMElement的类型加入到客户端的soap request中去即可:

OMElement idEle = fac.createOMElement("attchmentID", omNs);

idEle.setText(p_w_uploadID);

uploadFile.addChild(nameEle);

uploadFile.addChild(idEle);

env.getBody().addChild(uploadFile);

来看运行效果。

客户端:

上传d:/deployment/test.jpg文件

通向架构师的道路(十一)--之Axis2 Web Service(三) _service_07

通向架构师的道路(十一)--之Axis2 Web Service(三) _客户端_08


客户端收到服务端返回的”File saved successfully”即可在服务端的” D:/upload/axis2”目录中查询是否成功上传了该文件了

通向架构师的道路(十一)--之Axis2 Web Service(三) _客户端_09


可以看到,由于我们使用的是UUID因此每次上传,服务端的文件名都不会重复。

附录 UUID.java

package org.sky.axis2.util;


public class UUID {

protected static int count = 0;


public static synchronized String getUUID() {

  count++;

  long time = System.currentTimeMillis();


  String timePattern = Long.toHexString(time);

  int leftBit = 14 - timePattern.length();

  if (leftBit > 0) {

timePattern = "0000000000".substring(0, leftBit) + timePattern;

  }


  String uuid = timePattern

+ Long.toHexString(Double.doubleToLongBits(Math.random()))

+ Long.toHexString(Double.doubleToLongBits(Math.random()))

+ "000000000000000000";


  uuid = uuid.substring(0, 32).toUpperCase();


  return uuid;

}

}