com.legstar.proxy.invoke.jaxws.WebServiceInvoker.java Source code

Java tutorial

Introduction

Here is the source code for com.legstar.proxy.invoke.jaxws.WebServiceInvoker.java

Source

/*******************************************************************************
 * Copyright (c) 2010 LegSem.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     LegSem - initial API and implementation
 ******************************************************************************/
package com.legstar.proxy.invoke.jaxws;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.soap.Detail;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.SOAPFault;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import javax.xml.ws.soap.SOAPFaultException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.legstar.coxb.util.JAXBAnnotationException;
import com.legstar.coxb.util.JAXBElementDescriptor;
import com.legstar.coxb.util.NameUtil;
import com.legstar.proxy.invoke.AbstractProxyInvoker;
import com.legstar.proxy.invoke.ProxyInvokerException;
import com.legstar.proxy.invoke.ReflectOperationProxy;

/**
 * This provides a direct Web Service invoker via {@link java.xml.ws.Dispatch}.
 * <p/>
 * The class is immutable. All parameters are passed at construction time.
 * Limitations:
 * <ul>
 * <li>SOAPBinding.SOAP11HTTP_BINDING there is no support for MTOM or SOAP 1.2</li>
 * <li>Service.Mode.PAYLOAD JAXB Objects are used to create the SOAP payload</li>
 * <li>No support for authentication against target Web Service</li>
 * </ul>
 * 
 */
public class WebServiceInvoker extends AbstractProxyInvoker {

    /* ====================================================================== */
    /* = Constants section = */
    /* ====================================================================== */
    /** URL locating target Web service WSDL. */
    public static final String WSDL_URL_PROPERTY = "wsdlUrl";

    /** Target Web service WSDL namespace. */
    public static final String WSDL_TARGET_NAMESPACE_PROPERTY = "wsdlTargetNamespace";

    /** Target Web service WSDL service name. */
    public static final String WSDL_SERVICE_NAME_PROPERTY = "wsdlServiceName";

    /** Target Web service WSDL port name. */
    public static final String WSDL_PORT_NAME_PROPERTY = "wsdlPortName";

    /* The following set of parameters is the same as ReflectOperationProxy */

    /** Request JAXB type configuration parameter. */
    public static final String REQUEST_JAXB_TYPE_PROPERTY = ReflectOperationProxy.REQUEST_JAXB_TYPE_PROPERTY;

    /** Request JAXB package name configuration parameter. */
    public static final String REQUEST_JAXB_PACKAGE_NAME_PROPERTY = ReflectOperationProxy.REQUEST_JAXB_PACKAGE_NAME_PROPERTY;

    /** Response JAXB type. */
    public static final String RESPONSE_JAXB_TYPE_PROPERTY = ReflectOperationProxy.RESPONSE_JAXB_TYPE_PROPERTY;

    /** Response JAXB package name. */
    public static final String RESPONSE_JAXB_PACKAGE_NAME_PROPERTY = ReflectOperationProxy.RESPONSE_JAXB_PACKAGE_NAME_PROPERTY;

    /* ====================================================================== */
    /* = Properties section = */
    /* ====================================================================== */
    /** The WSDL URL. */
    private String mWsdlUrl;

    /** The WSDL target namespace. */
    private String mWsdlTargetNamespace;

    /** The WSDL service name. */
    private String mWsdlServiceName;

    /** The WSDL port name. */
    private String mWsdlPortName;

    /** The request descriptor. */
    private JAXBElementDescriptor mRequestElementDescriptor;

    /** The response descriptor. */
    private JAXBElementDescriptor mResponseElementDescriptor;

    /**
     * The dispatcher is thread safe starting with JAX-WS RI 2.1.2 provided we
     * don't change the JAXB classes.
     */
    private final Dispatch<Object> _dispatcher;

    /** Logger. */
    private final Log _log = LogFactory.getLog(getClass());

    /**
     * Standard constructor. The configuration parameters supported are:
     * <ul>
     * <li>wsdlUrl: Target web service wsdl URL</li>
     * <li>wsdlTargetNamespace: Target web service WSDL namespace</li>
     * <li>wsdlServiceName: Target web service name</li>
     * <li>wsdlPortName: Target web service port name</li>
     * <li>requestJaxbType: JAXB request type</li>
     * <li>requestJaxbPackageName: JAXB request package name</li>
     * <li>responseJaxbType: JAXB response type</li>
     * <li>responseJaxbPackageName: JAXB response package name</li>
     * </ul>
     * 
     * @param config configuration parameters
     * @throws WebServiceInvokerException if configuration is wrong
     */
    public WebServiceInvoker(final Map<String, String> config) throws WebServiceInvokerException {

        super(config);

        mWsdlUrl = config.get(WSDL_URL_PROPERTY);
        if (mWsdlUrl == null || mWsdlUrl.length() == 0) {
            throw new WebServiceInvokerException(
                    "You must specify a wsdl URL using the " + WSDL_URL_PROPERTY + " attribute");
        }
        mWsdlTargetNamespace = config.get(WSDL_TARGET_NAMESPACE_PROPERTY);
        if (mWsdlTargetNamespace == null || mWsdlTargetNamespace.length() == 0) {
            throw new WebServiceInvokerException("You must specify a wsdl target namespace using the "
                    + WSDL_TARGET_NAMESPACE_PROPERTY + " attribute");
        }
        mWsdlServiceName = config.get(WSDL_SERVICE_NAME_PROPERTY);
        if (mWsdlServiceName == null || mWsdlServiceName.length() == 0) {
            throw new WebServiceInvokerException(
                    "You must specify a service name using the " + WSDL_SERVICE_NAME_PROPERTY + " attribute");
        }
        mWsdlPortName = config.get(WSDL_PORT_NAME_PROPERTY);
        if (mWsdlPortName == null || mWsdlPortName.length() == 0) {
            throw new WebServiceInvokerException(
                    "You must specify a port name using the " + WSDL_PORT_NAME_PROPERTY + " attribute");
        }

        String requestJaxbType = config.get(REQUEST_JAXB_TYPE_PROPERTY);
        if (requestJaxbType == null || requestJaxbType.length() == 0) {
            throw new WebServiceInvokerException(
                    "You must specify a jaxb request type using the " + REQUEST_JAXB_TYPE_PROPERTY + " attribute");
        }
        String requestJaxbPackageName = config.get(REQUEST_JAXB_PACKAGE_NAME_PROPERTY);
        if (requestJaxbPackageName == null || requestJaxbPackageName.length() == 0) {
            throw new WebServiceInvokerException("You must specify a jaxb request package name using the "
                    + REQUEST_JAXB_PACKAGE_NAME_PROPERTY + " attribute");
        }
        String responseJaxbType = config.get(RESPONSE_JAXB_TYPE_PROPERTY);
        if (responseJaxbType == null || responseJaxbType.length() == 0) {
            throw new WebServiceInvokerException("You must specify a jaxb response type using the "
                    + RESPONSE_JAXB_TYPE_PROPERTY + " attribute");
        }
        String responseJaxbPackageName = config.get(RESPONSE_JAXB_PACKAGE_NAME_PROPERTY);
        if (responseJaxbPackageName == null || responseJaxbPackageName.length() == 0) {
            throw new WebServiceInvokerException("You must specify a jaxb response package name using the "
                    + RESPONSE_JAXB_PACKAGE_NAME_PROPERTY + " attribute");
        }

        try {
            mRequestElementDescriptor = new JAXBElementDescriptor(requestJaxbPackageName, requestJaxbType);
            mResponseElementDescriptor = new JAXBElementDescriptor(responseJaxbPackageName, responseJaxbType);
            _dispatcher = createDispatcher();
        } catch (JAXBAnnotationException e) {
            throw new WebServiceInvokerException(e);
        }

        if (_log.isDebugEnabled()) {
            _log.debug("WebServiceInvoker setup configuration:");
            _log.debug("Wsdl Url=" + getWsdlUrl());
            _log.debug("Wsdl service name=" + getWsdlServiceName());
            _log.debug("Wsdl target namespace=" + getWsdlTargetNamespace());
            _log.debug("Wsdl port=" + getWsdlPortName());
            _log.debug("Request element=[" + getRequestElementDescriptor().toString() + "]");
            _log.debug("Response element=[" + getResponseElementDescriptor().toString() + "]");
            _log.debug("Dispatcher=[" + _dispatcher + "]");
        }
    }

    /**
     * {@inheritDoc}
     * 
     * Had to synchronize because the JAX-WS RI dispatcher 2.1.3/2.1.4 is not
     * threadsafe (@see WebServiceInvokerTest)
     * */
    @SuppressWarnings("unchecked")
    public synchronized <T> T invoke(final String requestID, final Object oRequest) throws ProxyInvokerException {
        if (_log.isDebugEnabled()) {
            _log.debug("About to call invokeDispatch for service=" + getWsdlServiceName() + " request ID="
                    + requestID);
        }
        Object replyObject = invokeDispatch(oRequest);
        if (_log.isDebugEnabled()) {
            _log.debug("Returned from invokeDispatch for service=" + getWsdlServiceName() + " request ID="
                    + requestID);
        }
        return (T) replyObject;
    }

    /**
     * @return a JAXBContext for request and response types
     * @throws WebServiceInvokerException if JABContext cannot be created
     */
    private JAXBContext createJAXBContext() throws WebServiceInvokerException {
        try {
            if (getResponseElementDescriptor().getJaxbPackageName()
                    .compareTo(getRequestElementDescriptor().getJaxbPackageName()) == 0) {
                return JAXBContext.newInstance(getRequestElementDescriptor().getObjectFactory().getClass());
            } else {
                return JAXBContext.newInstance(getRequestElementDescriptor().getObjectFactory().getClass(),
                        getResponseElementDescriptor().getObjectFactory().getClass());
            }
        } catch (JAXBException e) {
            throw new WebServiceInvokerException(e);
        }
    }

    /**
     * Call the JAXWS dispatch method.
     * <p/>
     * The parameters passed to dispatch.invoke must have an XmlRootElement
     * annotation or otherwise must be wrapped in a JAXBElement.
     * <p/>
     * Similarly, the reply could be wrapped in a JAXBElement in which case we
     * return its inner content model.
     * 
     * @param oRequest the request object
     * @return the object response
     * @throws WebServiceInvokerException if dispatch fails
     */
    private Object invokeDispatch(final Object oRequest) throws WebServiceInvokerException {

        try {
            Object oResponse;
            if (getRequestElementDescriptor().isXmlRootElement()) {
                oResponse = _dispatcher.invoke(oRequest);
            } else {
                JAXBElement<?> jeRequest = getJAXBElement(getRequestElementDescriptor().getObjectFactory(),
                        getRequestElementDescriptor().getElementName(), oRequest);
                oResponse = _dispatcher.invoke(jeRequest);
            }
            if (_log.isDebugEnabled()) {
                _log.debug("invokeDispatch returned " + oResponse);
            }
            if (getResponseElementDescriptor().isXmlRootElement()) {
                return oResponse;
            } else {
                return ((JAXBElement<?>) oResponse).getValue();
            }

        } catch (SOAPFaultException e) {
            throw new WebServiceInvokerException(getFaultReasonText(e), e);
        }
    }

    /**
     * Try to extract something meaningful from a SOAP Fault.
     * 
     * @param e the SOAP Fault exception
     * @return a fault description
     */
    @SuppressWarnings("rawtypes")
    public String getFaultReasonText(final SOAPFaultException e) {
        if (_log.isDebugEnabled()) {
            SOAPFault fault = e.getFault();
            if (fault != null) {
                QName code = fault.getFaultCodeAsQName();
                String string = fault.getFaultString();
                String actor = fault.getFaultActor();
                _log.debug("SOAP fault contains: ");
                _log.debug("  Fault code = " + code.toString());
                _log.debug("  Local name = " + code.getLocalPart());
                _log.debug("  Namespace prefix = " + code.getPrefix() + ", bound to " + code.getNamespaceURI());
                _log.debug("  Fault string = " + string);
                if (actor != null) {
                    _log.debug("  Fault actor = " + actor);
                }
                Detail detail = fault.getDetail();
                if (detail != null) {
                    Iterator entries = detail.getDetailEntries();
                    while (entries.hasNext()) {
                        DetailEntry newEntry = (DetailEntry) entries.next();
                        String value = newEntry.getValue();
                        _log.debug("  Detail entry = " + value);
                    }
                }
            } else {
                _log.debug(e);
            }
        }
        SOAPFault fault = e.getFault();
        if (fault != null) {
            StringBuffer faultMessage = new StringBuffer(e.getFault().getFaultString());
            Detail detail = fault.getDetail();
            if (detail != null) {
                Iterator entries = detail.getDetailEntries();
                while (entries.hasNext()) {
                    DetailEntry newEntry = (DetailEntry) entries.next();
                    faultMessage.append(" [" + newEntry.getValue() + "]");
                }
            }
            return faultMessage.toString();
        } else {
            return e.getMessage();
        }

    }

    /**
     * Creates a JAX-WS dispatcher.
     * 
     * @return an instance of jaxws dynamic client invoker.
     * @throws WebServiceInvokerException if attempt to instantiate dispatcher
     *             fails
     */
    public Dispatch<Object> createDispatcher() throws WebServiceInvokerException {
        QName serviceQname = new QName(getWsdlTargetNamespace(), getWsdlServiceName());
        QName portQname = new QName(getWsdlTargetNamespace(), getWsdlPortName());
        Service service = Service.create(serviceQname);
        service.addPort(portQname, SOAPBinding.SOAP11HTTP_BINDING, getWsdlUrl());
        Dispatch<Object> dispatcher = service.createDispatch(portQname, createJAXBContext(), Service.Mode.PAYLOAD);

        if (_log.isDebugEnabled()) {
            _log.debug("New javax.xml.ws.Dispatch created for " + getWsdlServiceName());
        }
        return dispatcher;
    }

    /**
     * Root elements can be created with special methods in the JAXB object
     * factory. This method will use reflection to create a JAXBElement.
     * 
     * @param objectFactory the JAXB object factory
     * @param elementName the XML element name
     * @param type an occurrence of the element type
     * @return a JAXBElement
     * @throws WebServiceInvokerException if the JAXBElement cannot be created
     */
    private JAXBElement<?> getJAXBElement(final Object objectFactory, final String elementName, final Object type)
            throws WebServiceInvokerException {
        try {
            String createName = "create" + NameUtil.toClassName(elementName);
            Method creator = objectFactory.getClass().getMethod(createName, type.getClass());
            return (JAXBElement<?>) creator.invoke(objectFactory, type);
        } catch (IllegalAccessException e) {
            throw new WebServiceInvokerException(e);
        } catch (SecurityException e) {
            throw new WebServiceInvokerException(e);
        } catch (NoSuchMethodException e) {
            throw new WebServiceInvokerException(e);
        } catch (IllegalArgumentException e) {
            throw new WebServiceInvokerException(e);
        } catch (InvocationTargetException e) {
            throw new WebServiceInvokerException(e);
        }
    }

    /**
     * @return the request element descriptor
     */
    public JAXBElementDescriptor getRequestElementDescriptor() {
        return mRequestElementDescriptor;
    }

    /**
     * @return the response element descriptor
     */
    public JAXBElementDescriptor getResponseElementDescriptor() {
        return mResponseElementDescriptor;
    }

    /**
     * @return the WSDL service name
     */
    public String getWsdlServiceName() {
        return mWsdlServiceName;
    }

    /**
     * @return the WSDL port name
     */
    public String getWsdlPortName() {
        return mWsdlPortName;
    }

    /**
     * @return the WSDL target namespace
     */
    public String getWsdlTargetNamespace() {
        return mWsdlTargetNamespace;
    }

    /**
     * @return the WSDL URL
     */
    public String getWsdlUrl() {
        return mWsdlUrl;
    }

}