2016年11月25日 星期五

SOAP

※SOAPBody

try {
    // 查API javax.xml.soap的MessageFactory和SOAPMessage就會有以下的順序
    MessageFactory factory = MessageFactory.newInstance();
    SOAPMessage msg = factory.createMessage();
    SOAPPart part = msg.getSOAPPart();
    SOAPEnvelope envlope = part.getEnvelope();
    SOAPBody body = envlope.getBody();
    
    // <xx:bMethod xmlns:xx="!@#" />
    SOAPBodyElement element = body.addBodyElement(new QName("!@#", "bMethod", "xx"));
    element.setValue("1"); // element.setTextContent("1");
    element.addChildElement("p1").setValue("2");
    element.addChildElement("p2").setValue("3");
    msg.writeTo(System.out);
} catch (SOAPException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
--------------------
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <xx:bMethod xmlns:xx="!@#">
            1
            <p1>2</p1>
            <p2>3</p2>
        </xx:bMethod>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

※上面的測試是不需要啟動伺服器的



※主要程式

※IMessage.java

package webservice;
    
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import jaxb.Dept;
    
@WebService
public interface IMessage {
    @WebResult(name = "dept") // 使用JAXB的unmarshal時,一定要叫dept,才能轉換,否則會出預期xxx的錯
    public Dept aMethod(@WebParam(name = "dept") Dept d);// 使用XPath的NodeList時,一定要叫dept(預設是arg0),這樣XML才會正確,否則都找不到資料
    
    /*
        @WebResult的name裡面不要有空格,將SOAPMessage傳進來時,
        例如將「bFunctionReturn」改成「b function return」之後,
        會出「關聯元素類型 "b" 的屬性名稱 "function"
        之後必須緊接 ' = ' 字元。」的錯
    */
    @WebResult(name = "bFunctionReturn")
    public String bMethod(@WebParam(name = "num") Integer i);
    
    @WebResult(name = "dept") // 名稱要對應到QName,然後給HeaderElement
    public Dept cMethod(@WebParam(name = "headerName", header = true) String headInfo);
}


※MessageImpl.java

package webservice;
    
import java.util.ArrayList;
import java.util.List;
import javax.jws.WebService;
import jaxb.Dept;
import jaxb.Employee;
    
@WebService(endpointInterface = "webservice.IMessage")
public class MessageImpl implements IMessage {
    @Override
    public Dept aMethod(Dept dept) {
        System.out.println("a方法" + dept);
        return dept;
    
    }
    
    @Override
    public String bMethod(Integer i) {
        System.out.println("b方法");
        return i + "b";
    }
    
    @Override
    public Dept cMethod(String headInfo) {
        System.out.println("headInfo=" + headInfo);
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("e1", 1));
        list.add(new Employee("e2", 2));
        return new Dept("d1", 123, list);
    }
}


※MyServer.java

package webservice;
    
import javax.xml.ws.Endpoint;
    
public class MyServer {
    public static void main(String[] args) {
        Endpoint.publish("http://localhost:8888/aaa/bbb/ccc", new MessageImpl());
        System.out.println("server is start...");
    }
}

※打開xxx~~~?wsdl的網頁

<definitions
    ...
    targetNamespace="http://webservice/" 
    name="MessageImplService">
    
    ...
    <service name="MessageImplService">
        <port name="MessageImplPort" binding="tns:MessageImplPortBinding">
            <soap:address location="http://localhost:8888/aaa/bbb/ccc"/>
        </port>
    </service>
</definitions>

※會用到definitions標籤的targetNamespace和name、還有service的子標籤port的name屬性



※測試Service.Mode.MESSAGE

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
    
String ns = "http://webservice/";
try {
    URL url = new URL("http://localhost:8888/aaa/bbb/ccc?wsdl");
    QName sqname = new QName(ns, "MessageImplService");
    Service service = Service.create(url, sqname);
    
    QName qname = new QName(ns, "MessageImplPort");
    Dispatch<SOAPMessage> dispatch = service.createDispatch(qname, SOAPMessage.class, Service.Mode.MESSAGE);
    
    MessageFactory factory = MessageFactory.newInstance();
    SOAPMessage msg = factory.createMessage();
    SOAPPart part = msg.getSOAPPart();
    SOAPEnvelope envlope = part.getEnvelope();
    SOAPBody body = envlope.getBody();
    
    SOAPBodyElement element = body.addBodyElement(new QName(ns, "bMethod", "xx"));
    // 不打num會接收不到,物件會接收「null」字串;基本型別接收「0」
    element.addChildElement("num").setValue("2");
    msg.writeTo(System.out);
    System.out.println();
    
    // 將msg傳到server
    SOAPMessage resMsg = dispatch.invoke(msg);
    resMsg.writeTo(System.out);
    System.out.println();
    
    // 從server傳回來的訊息
    Document doc = resMsg.getSOAPPart().getEnvelope().getBody().extractContentAsDocument();
    NodeList nodeList = doc.getElementsByTagName("bFunctionReturn");
    
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        System.out.println(node.getTextContent()); // 2b
    }
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (SOAPException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
--------------------
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <xx:bMethod xmlns:xx="http://webservice/">
            <num>2</num>
        </xx:bMethod>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
--------------------
<S:Envelope
    xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    
    <SOAP-ENV:Header/>
    <S:Body>
        <ns2:bMethodResponse xmlns:ns2="http://webservice/">
            <bFunctionReturn>2b</bFunctionReturn>
        </ns2:bMethodResponse>
    </S:Body>
</S:Envelope>



※Service.Mode.PAYLOAD

import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.NodeList;
import jaxb.Dept;
import jaxb.Employee;
    
public void serviceModePAYLOADExample() {
    String ns = "http://webservice/";
    try {
        URL url = new URL("http://localhost:8888/aaa/bbb/ccc?wsdl");
        QName sqname = new QName(ns, "MessageImplService");
        Service service = Service.create(url, sqname);
    
        QName qname = new QName(ns, "MessageImplPort");
        Dispatch<Source> dispatch = service.createDispatch(qname, Source.class, Service.Mode.PAYLOAD);
    
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("Bruce", 123));
        list.add(new Employee("Jacky", 456));
        list.add(new Employee("Mary", 789));
        Dept dept = new Dept("information", 2, list);
    
        JAXBContext jaxb = JAXBContext.newInstance(Dept.class);
        Marshaller ms = jaxb.createMarshaller();
    
        // 去掉前面的<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        ms.setProperty(Marshaller.JAXB_FRAGMENT, true);
        StringWriter out = new StringWriter();
        ms.marshal(dept, out);
    
        StringBuffer payload = new StringBuffer();
        payload.append("<xxx:aMethod xmlns:xxx=\"");
        payload.append(ns);
        payload.append("\">");
        payload.append(out.toString());
        payload.append("</xxx:aMethod>");
    
        System.out.println(payload);
        StreamSource rs = new StreamSource(new StringReader(payload.toString()));
    
        Source response = (Source) dispatch.invoke(rs);
        Transformer tran = TransformerFactory.newInstance().newTransformer();
        DOMResult result = new DOMResult();
        tran.transform(response, result);
    
        XPath xpath = XPathFactory.newInstance().newXPath();
        // 如果是找dept它的子元素,能找到,但unmarshal會有錯誤
        NodeList nodeList = (NodeList) xpath.evaluate("//dept", result.getNode(), XPathConstants.NODESET);
        System.out.println(nodeList.getLength());
        for (int i = 0; i < nodeList.getLength(); i++) {
            System.out.println(nodeList.item(i));
            Dept ru = (Dept) jaxb.createUnmarshaller().unmarshal(nodeList.item(i));
            System.out.println(ru.getName() + System.getProperty("line.separator"));
    
            for (Employee emp : ru.getEmps()) {
                System.out.println(emp.getName());
            }
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (JAXBException e) {
        e.printStackTrace();
    } catch (TransformerConfigurationException e) {
        e.printStackTrace();
    } catch (TransformerFactoryConfigurationError | TransformerException e) {
        e.printStackTrace();
    } catch (XPathExpressionException e) {
        e.printStackTrace();
    }
}



※SOAP Header測試

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import jaxb.Dept;
    
public void soapHeadTest() {
    String ns = "http://webservice/";
    try {
        URL url = new URL("http://localhost:8888/aaa/bbb/ccc?wsdl");
        QName sqname = new QName(ns, "MessageImplService");
        Service service = Service.create(url, sqname);
    
        QName qname = new QName(ns, "MessageImplPort");
        Dispatch<SOAPMessage> dispatch = service.createDispatch(qname, SOAPMessage.class, Service.Mode.MESSAGE);
    
        MessageFactory factory = MessageFactory.newInstance();
        SOAPMessage msg = factory.createMessage();
        SOAPPart part = msg.getSOAPPart();
        SOAPEnvelope envlope = part.getEnvelope();
        SOAPBody body = envlope.getBody();
    
        SOAPHeader header = envlope.getHeader();
        if (header == null) {
            header = envlope.addHeader();
        }
        SOAPHeaderElement headElement = header.addHeaderElement(new QName(ns, "headerName", "xx"));
        headElement.setValue("header value!");
    
        body.addBodyElement(new QName(ns, "cMethod", "xx"));
        msg.writeTo(System.out);
        System.out.println();
    
        // 將msg傳到server
        SOAPMessage resMsg = dispatch.invoke(msg);
        resMsg.writeTo(System.out);
        System.out.println();
    
        // 從server傳回來的訊息
        Document doc = resMsg.getSOAPPart().getEnvelope().getBody().extractContentAsDocument();
        // Document doc = resMsg.getSOAPBody().extractContentAsDocument();
        NodeList nodeList = doc.getElementsByTagName("dept");
    
        JAXBContext jaxb = JAXBContext.newInstance(Dept.class);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Dept dept = (Dept) jaxb.createUnmarshaller().unmarshal(nodeList.item(i));
            System.out.println(dept.getName());
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (SOAPException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (JAXBException e) {
        e.printStackTrace();
    }
}




※Exception

加入Exception後,會在wsdl的網址出現fault的標籤

@WebResult(name = "dept")
public Dept dMethod(@WebParam(name = "name") String name, @WebParam(name = "did") int did) throws DeptException;
--------------------
@Override
public Dept dMethod(String name, int did) throws DeptException {
    if ("xxx".equals(name) && did == 111) {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("e1", 1));
        list.add(new Employee("e2", 2));
        Dept dept = new Dept("xxx", 111, list);
        return dept;
    }
    
    /*
     * 使用RuntimeException,也會在伺服器端的console報錯,因為DeptException在wsdl的網頁有截取(fault
     * );而RuntimeException沒有
     * 只要有在wsdl有設定Exception,截取到就會直接往客戶端拋,所以伺服器端沒有截取錯誤訊息
     * 如沒有在wsdl設定,則伺服器端和客戶端接會拋出訊息
     */
    // throw new RuntimeException(name + " and " + did + " not match!" +
    // System.getProperty("line.separator"));
    
    throw new DeptException(name + " and " + did + " not match!" + System.getProperty("line.separator"));
}

※name是xxx,did是111才不會出錯,但這裡要測試出錯,所以待會的測試類不要輸入這一組


※DeptException.java

public class DeptException extends Exception {
    public DeptException() {
        super();
    }
    
    public DeptException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
        super(arg0, arg1, arg2, arg3);
    }
    
    public DeptException(String message, Throwable cause) {
        super(message, cause);
    }
    
    public DeptException(String message) {
        super(message);
    }
    
    public DeptException(Throwable cause) {
        super(cause);
    }
}

※這裡的建構子都是用IDE產生的


※測試類

String ns = "http://webservice/";
try {
    URL url = new URL("http://localhost:8888/aaa/bbb/ccc?wsdl");
    QName sqname = new QName(ns, "MessageImplService");
    Service service = Service.create(url, sqname);
    
    QName qname = new QName(ns, "MessageImplPort");
    Dispatch<SOAPMessage> dispatch = service.createDispatch(qname, SOAPMessage.class, Service.Mode.MESSAGE);
    
    MessageFactory factory = MessageFactory.newInstance();
    SOAPMessage msg = factory.createMessage();
    SOAPPart part = msg.getSOAPPart();
    SOAPEnvelope envlope = part.getEnvelope();
    SOAPBody body = envlope.getBody();
    
    SOAPBodyElement element = body.addBodyElement(new QName(ns, "dMethod", "xx"));
    element.addChildElement("name").setValue("xxx");// 不打num會接收null;但如果是int會接收0
    element.addChildElement("did").setValue("222");
    msg.writeTo(System.out);
    System.out.println();
    
    // 將msg傳到server
    SOAPMessage resMsg = dispatch.invoke(msg);
    resMsg.writeTo(System.out);
    System.out.println();
    
    // 從server傳回來的訊息
    Document doc = resMsg.getSOAPBody().extractContentAsDocument();
    NodeList nodeList = doc.getElementsByTagName("dept");
    
    JAXBContext jaxb = JAXBContext.newInstance(Dept.class);
    for (int i = 0; i < nodeList.getLength(); i++) {
        Dept dept = (Dept) jaxb.createUnmarshaller().unmarshal(nodeList.item(i));
        for (Employee e : dept.getEmps()) {
            System.out.println(e.getName());
        }
    }
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (SOAPException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch (JAXBException e) {
    e.printStackTrace();
} catch (SOAPFaultException e) {
    // 不可截取DeptException,使用wsimport就可以知道
    System.out.println(e.getMessage());// call DeptException(String)
}





※Handler

LogicalHandler:獲取SOAPBody的訊息
SOAPHandler:獲取SOAPMessage的訊息
伺服器和客戶端都可以設定很多LH的和SH
客戶端都先處理LH,全部的LH處理完才處理SH
伺服器端則相反

參考網址:apache JAVAEE連結






@WebService(endpointInterface = "webservice.IMessage")
@HandlerChain(file="/soap/handler/handlers.xml")
public class MessageImpl implements IMessage {
    // ...
}

※這裡的@HandlerChain裡的xml路徑只要抓的到,在啟伺服器時才不會出錯


※handlers.xml

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
    <handler-chain>
        <handler>
            <handler-name>ooo.xxx.z1</handler-name>
            <handler-class>soap.handler.Handler1</handler-class>
        </handler>
        <handler>
            <handler-name>ooo.xxx.z2</handler-name>
            <handler-class>soap.handler.Handler2</handler-class>
        </handler>
    </handler-chain>
</handler-chains>

※寫了兩支處理handler的xml,所以也要實作出來,將上面的網址複製下來改即可


※Handler1.xml

public class Handler1 implements SOAPHandler<SOAPMessageContext> {
    @Override
    public void close(MessageContext arg0) {
        System.out.println("s1 close");
    }
    
    @Override
    public boolean handleFault(SOAPMessageContext arg0) {
        System.out.println("s1 handleFault");
        return true;
    }
    
    @Override
    public boolean handleMessage(SOAPMessageContext arg0) {
        System.out.println("s1 handleMessage");
        return true;
    }
    
    @Override
    public Set<QName> getHeaders() {
        System.out.println("s1 getHeaders");
        return null;
    }
}



※Handler2.java

public class Handler2 implements SOAPHandler<SOAPMessageContext> {
    @Override
    public void close(MessageContext arg0) {
        System.out.println("s2 close");
    }
    
    @Override
    public boolean handleFault(SOAPMessageContext arg0) {
        System.out.println("s2 handleFault");
        return true;
    }
    
    @Override
    public boolean handleMessage(SOAPMessageContext arg0) {
        System.out.println("s2 handleMessage");
        return true;
    }
    
    @Override
    public Set<QName> getHeaders() {
        System.out.println("s2 getHeaders");
        return null;
    }
}

※測試時還是用Exception的測試類

※沒有錯時的結果為:
s1 getHeaders
s2 getHeaders
server is start...
s2 handleMessage
s1 handleMessage
s1 handleMessage
s2 handleMessage
s1 close
s2 close

※有錯時的結果為:
s1 getHeaders
s2 getHeaders
server is start...
s2 handleMessage
s1 handleMessage
s1 handleFault
s2 handleFault
s1 close
s2 close

※可以發現在啟何服器時都會先執行兩支的getHeaders方法,結束也都會執行close

※有錯時才會執行handleFault方法

※要注意handleMessage和其他是相反的,先執行s2;後執行s1
沒錯時,一支handleMessage是request和response,共兩次;有錯時只會執行一次

※handleMessage執行完才會執行Exception;使用RuntimeException還是會在伺服器端報錯

沒有留言:

張貼留言