Java tutorial
/* * 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; } } }