org.codehaus.enunciate.modules.xfire.EnunciatedJAXWSOperationBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.enunciate.modules.xfire.EnunciatedJAXWSOperationBinding.java

Source

/*
 * Copyright 2006-2008 Web Cohesion
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.codehaus.enunciate.modules.xfire;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.XFireRuntimeException;
import org.codehaus.xfire.exchange.InMessage;
import org.codehaus.xfire.exchange.MessageSerializer;
import org.codehaus.xfire.exchange.OutMessage;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.jaxb2.AttachmentMarshaller;
import org.codehaus.xfire.jaxb2.AttachmentUnmarshaller;
import org.codehaus.xfire.service.OperationInfo;
import org.codehaus.xfire.service.MessageInfo;
import org.codehaus.xfire.util.ClassLoaderUtils;
import org.xml.sax.SAXException;

import javax.jws.soap.SOAPBinding;
import javax.jws.WebParam;
import javax.xml.bind.*;
import javax.xml.bind.helpers.DefaultValidationEventHandler;
import javax.xml.bind.annotation.XmlType;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerFactory;
import javax.xml.parsers.DocumentBuilderFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;

/**
 * The binding for a JAXWS operation.
 *
 * @author Ryan Heaton
 */
public class EnunciatedJAXWSOperationBinding implements MessageSerializer {

    private static final Log LOG = LogFactory.getLog(EnunciatedJAXWSOperationBinding.class);

    private final JAXBContext jaxbContext;
    private final OperationBeanInfo requestInfo;
    private final OperationBeanInfo responseInfo;
    private ValidationEventHandler validationEventHandler = new DefaultValidationEventHandler();

    public EnunciatedJAXWSOperationBinding(OperationInfo op) throws XFireFault {
        this.requestInfo = getRequestInfo(op);
        this.responseInfo = getResponseInfo(op);
        ArrayList<Class> contextClasses = new ArrayList<Class>(2);
        if (this.requestInfo != null) {
            contextClasses.add(this.requestInfo.getBeanClass());
        }
        if (this.responseInfo != null) {
            contextClasses.add(this.responseInfo.getBeanClass());
        }

        try {
            this.jaxbContext = JAXBContext.newInstance(contextClasses.toArray(new Class[contextClasses.size()]));
        } catch (JAXBException e) {
            throw new XFireFault("Unable to create a binding for " + op.getMethod() + ".", e, XFireFault.RECEIVER);
        }
    }

    /**
     * Loads the set of input properties for the specified operation.
     *
     * @param op The operation.
     * @return The input properties, or null if none were found.
     */
    protected OperationBeanInfo getRequestInfo(OperationInfo op) throws XFireFault {
        Method method = op.getMethod();
        Class ei = method.getDeclaringClass();
        Package pckg = ei.getPackage();
        SOAPBinding.ParameterStyle paramStyle = SOAPBinding.ParameterStyle.WRAPPED;
        if (method.isAnnotationPresent(SOAPBinding.class)) {
            SOAPBinding annotation = method.getAnnotation(SOAPBinding.class);
            paramStyle = annotation.parameterStyle();
        } else if (ei.isAnnotationPresent(SOAPBinding.class)) {
            SOAPBinding annotation = ((SOAPBinding) ei.getAnnotation(SOAPBinding.class));
            paramStyle = annotation.parameterStyle();
        }

        boolean schemaValidate = method.isAnnotationPresent(SchemaValidate.class)
                || ei.isAnnotationPresent(SchemaValidate.class) || pckg.isAnnotationPresent(SchemaValidate.class);

        if (paramStyle == SOAPBinding.ParameterStyle.BARE) {
            //return a bare operation info.
            //it's not necessarily the first parameter type! there could be a header or OUT parameter...
            int paramIndex;
            WebParam annotation = null;
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            if (parameterAnnotations.length == 0) {
                throw new IllegalStateException("A BARE web service must have input parameters.");
            }

            PARAM_ANNOTATIONS: for (paramIndex = 0; paramIndex < parameterAnnotations.length; paramIndex++) {
                Annotation[] annotations = parameterAnnotations[paramIndex];
                for (Annotation candidate : annotations) {
                    if (candidate instanceof WebParam && !((WebParam) candidate).header()) {
                        WebParam.Mode mode = ((WebParam) candidate).mode();
                        switch (mode) {
                        case OUT:
                        case INOUT:
                            annotation = (WebParam) candidate;
                            break PARAM_ANNOTATIONS;
                        }
                    }
                }
            }

            if (annotation == null) {
                paramIndex = 0;
            }

            return new OperationBeanInfo(method.getParameterTypes()[paramIndex], null, op.getInputMessage(),
                    schemaValidate);
        } else {
            String requestWrapperClassName;
            RequestWrapper requestWrapperInfo = method.getAnnotation(RequestWrapper.class);
            if ((requestWrapperInfo != null) && (requestWrapperInfo.className() != null)
                    && (requestWrapperInfo.className().length() > 0)) {
                requestWrapperClassName = requestWrapperInfo.className();
            } else {
                StringBuilder builder = new StringBuilder(pckg == null ? "" : pckg.getName());
                if (builder.length() > 0) {
                    builder.append(".");
                }

                builder.append("jaxws.");

                String methodName = method.getName();
                builder.append(capitalize(methodName));
                requestWrapperClassName = builder.toString();
            }

            Class wrapperClass;
            try {
                wrapperClass = ClassLoaderUtils.loadClass(requestWrapperClassName, getClass());
            } catch (ClassNotFoundException e) {
                LOG.error("Unabled to find request wrapper class " + requestWrapperClassName + "... Operation "
                        + op.getQName() + " will not be able to recieve...");
                return null;
            }

            return new OperationBeanInfo(wrapperClass, loadOrderedProperties(wrapperClass), op.getInputMessage(),
                    schemaValidate);
        }

    }

    /**
     * Loads the set of output properties for the specified operation.
     *
     * @param op The operation.
     * @return The output properties.
     */
    protected OperationBeanInfo getResponseInfo(OperationInfo op) throws XFireFault {
        Method method = op.getMethod();
        Class ei = method.getDeclaringClass();
        Package pckg = ei.getPackage();
        SOAPBinding.ParameterStyle paramStyle = SOAPBinding.ParameterStyle.WRAPPED;
        if (method.isAnnotationPresent(SOAPBinding.class)) {
            SOAPBinding annotation = method.getAnnotation(SOAPBinding.class);
            paramStyle = annotation.parameterStyle();
        } else if (ei.isAnnotationPresent(SOAPBinding.class)) {
            SOAPBinding annotation = ((SOAPBinding) ei.getAnnotation(SOAPBinding.class));
            paramStyle = annotation.parameterStyle();
        }

        if (paramStyle == SOAPBinding.ParameterStyle.BARE) {
            //bare return type.
            //todo: it's not necessarily the return type! it could be an OUT parameter...
            return new OperationBeanInfo(method.getReturnType(), null, op.getOutputMessage());
        } else {
            String responseWrapperClassName;
            ResponseWrapper responseWrapperInfo = method.getAnnotation(ResponseWrapper.class);
            if ((responseWrapperInfo != null) && (responseWrapperInfo.className() != null)
                    && (responseWrapperInfo.className().length() > 0)) {
                responseWrapperClassName = responseWrapperInfo.className();
            } else {
                StringBuilder builder = new StringBuilder(pckg == null ? "" : pckg.getName());
                if (builder.length() > 0) {
                    builder.append(".");
                }

                builder.append("jaxws.");

                String methodName = method.getName();
                builder.append(capitalize(methodName)).append("Response");
                responseWrapperClassName = builder.toString();
            }

            Class wrapperClass;
            try {
                wrapperClass = ClassLoaderUtils.loadClass(responseWrapperClassName, getClass());
            } catch (ClassNotFoundException e) {
                LOG.debug("Unabled to find request wrapper class " + responseWrapperClassName + "... Operation "
                        + op.getQName() + " will not be able to send...");
                return null;
            }

            return new OperationBeanInfo(wrapperClass, loadOrderedProperties(wrapperClass), op.getOutputMessage());
        }
    }

    /**
     * Loads the property descriptors for the ordered properties of the specified class.
     *
     * @param wrapperClass The wrapper class.
     * @return The ordered property descriptors.
     */
    protected PropertyDescriptor[] loadOrderedProperties(Class wrapperClass) throws XFireFault {
        XmlType typeInfo = (XmlType) wrapperClass.getAnnotation(XmlType.class);
        if ((typeInfo == null) || (typeInfo.propOrder() == null)
                || ((typeInfo.propOrder().length == 1) && "".equals(typeInfo.propOrder()[0]))) {
            throw new XFireFault(
                    "Unable use use " + wrapperClass.getName() + " as a wrapper class: no propOrder specified.",
                    XFireFault.RECEIVER);
        }

        String[] propOrder = typeInfo.propOrder();
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(wrapperClass, Object.class);
        } catch (IntrospectionException e) {
            throw new XFireFault("Unable to introspect " + wrapperClass.getName(), e, XFireFault.RECEIVER);
        }

        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        PropertyDescriptor[] props = new PropertyDescriptor[propOrder.length];
        RESPONSE_PROPERTY_LOOP: for (int i = 0; i < propOrder.length; i++) {
            String property = propOrder[i];
            if ((property.length() > 1) && (!Character.isLowerCase(property.charAt(1)))) {
                //if the second letter is uppercase, javabean spec says the first character of the property is also to be kept uppercase.
                property = capitalize(property);
            }

            for (PropertyDescriptor descriptor : pds) {
                if (descriptor.getName().equals(property)) {
                    props[i] = descriptor;
                    continue RESPONSE_PROPERTY_LOOP;
                }
            }

            throw new XFireFault("Unknown property " + property + " on wrapper " + wrapperClass.getName(),
                    XFireFault.RECEIVER);
        }

        return props;
    }

    public void readMessage(InMessage message, MessageContext context) throws XFireFault {
        if (this.requestInfo == null) {
            throw new XFireFault("Unable to read message: no request info was found!", XFireFault.RECEIVER);
        }

        Object bean;
        try {
            Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
            XMLStreamReader streamReader = message.getXMLStreamReader();
            if (this.requestInfo.isSchemaValidate()) {
                try {
                    TransformerFactory xformFactory = TransformerFactory.newInstance();
                    DOMResult domResult = new DOMResult();
                    xformFactory.newTransformer().transform(new StAXSource(streamReader, true), domResult);
                    unmarshaller.getSchema().newValidator().validate(new DOMSource(domResult.getNode()));
                    streamReader = XMLInputFactory.newInstance()
                            .createXMLStreamReader(new DOMSource(domResult.getNode()));
                } catch (Exception e) {
                    throw new XFireRuntimeException("Unable to validate the request against the schema.");
                }
            }
            unmarshaller.setEventHandler(getValidationEventHandler());
            unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshaller(context));
            bean = unmarshaller.unmarshal(streamReader, this.requestInfo.getBeanClass()).getValue();
        } catch (JAXBException e) {
            throw new XFireRuntimeException("Unable to unmarshal type.", e);
        }

        List<Object> parameters = new ArrayList<Object>();
        if (this.requestInfo.isBare()) {
            //bare method, doesn't need to be unwrapped.
            parameters.add(bean);
        } else {
            for (PropertyDescriptor descriptor : this.requestInfo.getPropertyOrder()) {
                try {
                    parameters.add(descriptor.getReadMethod().invoke(bean));
                } catch (IllegalAccessException e) {
                    throw new XFireFault("Problem with property " + descriptor.getName() + " on "
                            + this.requestInfo.getBeanClass().getName() + ".", e, XFireFault.RECEIVER);
                } catch (InvocationTargetException e) {
                    throw new XFireFault("Problem with property " + descriptor.getName() + " on "
                            + this.requestInfo.getBeanClass().getName() + ".", e, XFireFault.RECEIVER);
                }
            }
        }

        message.setBody(parameters);
    }

    public void writeMessage(OutMessage message, XMLStreamWriter writer, MessageContext context) throws XFireFault {
        if (this.responseInfo == null) {
            throw new XFireFault("Unable to write message: no response info was found!", XFireFault.SENDER);
        }

        Class beanClass = this.responseInfo.getBeanClass();
        Object[] params = (Object[]) message.getBody();
        Object bean;
        if (this.responseInfo.isBare()) {
            //bare response.  we don't need to wrap it up.
            bean = new JAXBElement(this.responseInfo.getMessageInfo().getName(), this.responseInfo.getBeanClass(),
                    params[0]);
        } else {
            try {
                bean = beanClass.newInstance();
            } catch (Exception e) {
                throw new XFireFault("Problem instantiating response wrapper " + beanClass.getName() + ".", e,
                        XFireFault.RECEIVER);
            }

            PropertyDescriptor[] properties = this.responseInfo.getPropertyOrder();
            if (properties.length > 0) { //no properties implies a void method...
                if (properties.length != params.length) {
                    throw new XFireFault("There are " + params.length + " parameters to the out message but "
                            + properties.length + " properties on " + beanClass.getName(), XFireFault.RECEIVER);
                }

                for (int i = 0; i < properties.length; i++) {
                    PropertyDescriptor descriptor = properties[i];
                    try {
                        descriptor.getWriteMethod().invoke(bean, params[i]);
                    } catch (IllegalAccessException e) {
                        throw new XFireFault("Problem with property " + descriptor.getName() + " on "
                                + beanClass.getName() + ".", e, XFireFault.RECEIVER);
                    } catch (InvocationTargetException e) {
                        throw new XFireFault("Problem with property " + descriptor.getName() + " on "
                                + beanClass.getName() + ".", e, XFireFault.RECEIVER);
                    }
                }
            }
        }

        try {
            Marshaller marshaller = this.jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            marshaller.setAttachmentMarshaller(new AttachmentMarshaller(context));
            marshaller.marshal(bean, writer);
        } catch (JAXBException e) {
            throw new XFireRuntimeException("Unable to marshal type.", e);
        }
    }

    /**
     * Capitalizes a string.
     *
     * @param string The string to capitalize.
     * @return The capitalized value.
     */
    private String capitalize(String string) {
        return Character.toString(string.charAt(0)).toUpperCase() + string.substring(1);
    }

    /**
     * The validation event handler.
     *
     * @return The validation event handler.
     */
    public ValidationEventHandler getValidationEventHandler() {
        return validationEventHandler;
    }

    /**
     * The validation event handler.
     *
     * @param validationEventHandler The validation event handler.
     */
    public void setValidationEventHandler(ValidationEventHandler validationEventHandler) {
        this.validationEventHandler = validationEventHandler;
    }

    /**
     * A simple bean info for a wrapper class.
     */
    public static class OperationBeanInfo {

        private Class beanClass;
        private PropertyDescriptor[] propertyOrder;
        private MessageInfo messageInfo;
        private final boolean schemaValidate;

        public OperationBeanInfo(Class wrapperClass, PropertyDescriptor[] properties, MessageInfo message) {
            this(wrapperClass, properties, message, false);
        }

        public OperationBeanInfo(Class wrapperClass, PropertyDescriptor[] properties, MessageInfo message,
                boolean schemaValidate) {
            this.beanClass = wrapperClass;
            this.propertyOrder = properties;
            this.messageInfo = message;
            this.schemaValidate = schemaValidate;
        }

        /**
         * The wrapper class.
         *
         * @return The wrapper class.
         */
        public Class getBeanClass() {
            return beanClass;
        }

        /**
         * Whether the operation bean is bare.
         *
         * @return Whether the operation bean is bare.
         */
        public boolean isBare() {
            return propertyOrder == null;
        }

        /**
         * The ordered list of wrapper properties.
         *
         * @return The ordered list of wrapper properties.
         */
        public PropertyDescriptor[] getPropertyOrder() {
            return propertyOrder;
        }

        /**
         * The message info.
         *
         * @return The message info.
         */
        public MessageInfo getMessageInfo() {
            return messageInfo;
        }

        /**
         * Whether to schema-valiate this operation.
         *
         * @return Whether to schema-valiate this operation.
         */
        public boolean isSchemaValidate() {
            return schemaValidate;
        }

    }

}