Feb 17, 2011

Dynamic SOAP Web Services with Jax-WS

В документации не очень хорошо описано как создать динамический клиент с помощью Jax-WS. Для этого используется Dispatcher API.



Прежде всего надо добавить необходимую зависимость в проект.

...
  
    
      com.sun.xml.ws
      jaxws-rt
      2.2.3
    
  
...
  
      
          java.net
          http://download.java.net/maven/2/
      
  
...

Исходный код клиента - DynamicClient.java

package org.etf.soap;

import java.util.Map;
import javax.xml.soap.SOAPException;
import javax.xml.ws.soap.SOAPBinding;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

/**
 *
 * @author Illya.Yalovyy
 */
public final class DynamicClient {
    private final String namespace;
    private final Service service;
    private final Dispatch dispatch;
    private final MessageFactory messageFactory;

    public DynamicClient(String namespace, String serviceLocation, 
            String serviceName, String servicePort) throws SOAPException {

        messageFactory = MessageFactory.newInstance();

        this.namespace = namespace;

        final QName serviceQName = new QName(namespace, serviceName);
        final QName servicePortQName = new QName(namespace, servicePort);

        service = Service.create(serviceQName);
        service.addPort(servicePortQName, SOAPBinding.SOAP11HTTP_BINDING,
                serviceLocation);
        dispatch = service.createDispatch(servicePortQName, SOAPMessage.class,
                Service.Mode.MESSAGE);
    }

    public SOAPMessage invoke(WebOperation operation, Object... args) throws SOAPException {

        Validate.isTrue(args.length <= operation.getArgumentNames().length, "Too many arguments.");

        if (operation.getSoapActionUri() == null) {
            dispatch.getRequestContext().put(Dispatch.SOAPACTION_USE_PROPERTY, Boolean.FALSE);
            dispatch.getRequestContext().put(Dispatch.SOAPACTION_URI_PROPERTY, "");
        } else {
            dispatch.getRequestContext().put(Dispatch.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
            dispatch.getRequestContext().put(Dispatch.SOAPACTION_URI_PROPERTY, operation.getSoapActionUri());
        }

        SOAPMessage message = messageFactory.createMessage();

        SOAPPart soapPart = message.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPBody body = envelope.getBody();
        SOAPBodyElement operationElement = body.addBodyElement(new QName(namespace, 
                operation.getOperation(), StringUtils.defaultIfBlank(operation.getPrefix(), "")));

        for (int i = 0; i < args.length; i++) {
            SOAPElement argElement = operationElement.addChildElement(
                    new QName(operation.getArgumentNames()[i]));
            argElement.setValue(String.valueOf(args[i]));
        }
        
        return dispatch.invoke(message);
    }

    public static final class Builder {

        private String namespace, serviceLocation, serviceName, servicePort;

        public Builder setNamespace(String namespace) {
            this.namespace = namespace;
            return this;
        }

        public Builder setServiceLocation(String serviceLocation) {
            this.serviceLocation = serviceLocation;
            return this;
        }

        public Builder setServiceName(String serviceName) {
            this.serviceName = serviceName;
            return this;
        }

        public Builder setServicePort(String servicePort) {
            this.servicePort = servicePort;
            return this;
        }

        public DynamicClient build() throws SOAPException {
            Validate.notNull(namespace, "Namespace is required.");
            Validate.notNull(serviceLocation, "Service location is required.");
            Validate.notNull(serviceName, "Service name is required.");
            Validate.notNull(servicePort, "Service port is required.");
            
            return new DynamicClient(namespace, serviceLocation, serviceName, servicePort);
        }
    }
}
Вспомогательный класс для описания вызываемого метода - WebOperation.java
package org.etf.soap;

import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.lang.Validate;

/**
 *
 * @author Illya.Yalovyy
 */
public class WebOperation {
    private final String soapActionUri, operation, prefix;
    private final String[] argumentNames;

    public WebOperation(String soapActionUri, String operation, String prefix, String[] argumentNames) {
        this.soapActionUri = soapActionUri;
        this.operation = operation;
        this.prefix = prefix;
        this.argumentNames = argumentNames;
    }

    public String[] getArgumentNames() {
        return argumentNames;
    }

    public String getOperation() {
        return operation;
    }

    public String getPrefix() {
        return prefix;
    }

    public String getSoapActionUri() {
        return soapActionUri;
    }

    public static final class Builder {
        private String soapActionUri, operation, prefix;
        private ArrayList argumentNames = new ArrayList();

        public Builder setArgumentNames(String... argumentNames) {
            this.argumentNames.clear();
            this.argumentNames.addAll(Arrays.asList(argumentNames));
            return this;
        }

        public Builder setOperation(String operation) {
            this.operation = operation;
            return this;
        }

        public Builder setPrefix(String prefix) {
            this.prefix = prefix;
            return this;
        }

        public Builder setSoapActionUri(String soapActionUri) {
            this.soapActionUri = soapActionUri;
            return this;
        }

        public Builder addArgumentName(String name) {
            this.argumentNames.add(name);
            return this;
        }

        public WebOperation build() {
            Validate.notNull(operation, "Operation name is required");
            return new WebOperation(soapActionUri, operation, prefix, 
                    argumentNames.toArray(new String[argumentNames.size()]));
        }
    }
}
Вспомогательный класс для печати результата на экран - XmlHelper.java
package org.etf.util;

import java.io.StringWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;

/**
 *
 * @author Illya.Yalovyy
 */
public final class XmlHelper {
    public static final String INDENTAMOUNT = "{http://xml.apache.org/xslt}indent-amount";

    private XmlHelper() {}

    public static void dumpNode(Node node) {
        try {
            final StringWriter writer = new StringWriter();
            final Source source = new DOMSource(node);
            final Result result = new StreamResult(writer);
            Transformer t = TransformerFactory.newInstance().newTransformer();
            t.setOutputProperty(OutputKeys.METHOD, "xml");
            t.setOutputProperty(OutputKeys.INDENT, "yes");
            t.setOutputProperty(OutputKeys.STANDALONE, "yes");
            t.setOutputProperty(INDENTAMOUNT,"2");
            t.transform(source, result);
            System.out.println(writer.toString());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }
}
Пример использовани динамического клиента:
        final DynamicClient mathertelClient = new DynamicClient.Builder()
                .setNamespace("http://www.mathertel.de/CalcFactors/")
                .setServiceLocation("http://www.mathertel.de/AJAXEngine/S02_AJAXCoreSamples/CalcService.asmx")
                .setServiceName("CalcService")
                .setServicePort("CalcServiceSoap")
                .build();

        final WebOperation addIntegerWebOperation = new WebOperation.Builder()
                .setSoapActionUri("http://www.mathertel.de/CalcFactors/AddInteger")
                .setOperation("AddInteger")
                .addArgumentName("number1")
                .addArgumentName("number2")
                .build();

        SOAPMessage response;
        response = mathertelClient.invoke(addIntegerWebOperation, 3, 4);
        XmlHelper.dumpNode(response.getSOAPBody());

        response = mathertelClient.invoke(addIntegerWebOperation, 21, 43);
        XmlHelper.dumpNode(response.getSOAPBody());
Полные исходные коды проекта: https://svn.kenai.com/svn/mce~mce

No comments: