| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.axis2.jaxws.description.impl; |
| |
| import org.apache.axis2.AxisFault; |
| import org.apache.axis2.description.AxisService; |
| import org.apache.axis2.description.Parameter; |
| import org.apache.axis2.jaxws.ExceptionFactory; |
| import org.apache.axis2.jaxws.description.AttachmentDescription; |
| import org.apache.axis2.jaxws.description.AttachmentType; |
| import org.apache.axis2.jaxws.description.EndpointDescription; |
| import org.apache.axis2.jaxws.description.EndpointDescriptionWSDL; |
| import org.apache.axis2.jaxws.description.builder.DescriptionBuilderComposite; |
| import static org.apache.axis2.jaxws.description.builder.MDQConstants.CONSTRUCTOR_METHOD; |
| |
| import org.apache.axis2.jaxws.description.builder.MDQConstants; |
| import org.apache.axis2.jaxws.description.builder.MethodDescriptionComposite; |
| import org.apache.axis2.jaxws.description.builder.WebMethodAnnot; |
| import org.apache.axis2.jaxws.description.xml.handler.HandlerChainsType; |
| import org.apache.axis2.jaxws.i18n.Messages; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import javax.wsdl.Binding; |
| import javax.wsdl.BindingOperation; |
| import javax.wsdl.Operation; |
| import javax.wsdl.extensions.mime.MIMEContent; |
| import javax.wsdl.extensions.mime.MIMEMultipartRelated; |
| import javax.wsdl.extensions.mime.MIMEPart; |
| import javax.wsdl.extensions.soap.SOAPBody; |
| import javax.wsdl.extensions.soap.SOAPHeader; |
| import javax.wsdl.extensions.soap12.SOAP12Body; |
| import javax.wsdl.extensions.soap12.SOAP12Header; |
| import javax.xml.bind.JAXBContext; |
| import javax.xml.bind.JAXBElement; |
| import javax.xml.bind.Unmarshaller; |
| import javax.xml.namespace.QName; |
| import javax.xml.ws.handler.Handler; |
| import javax.xml.ws.handler.soap.SOAPHandler; |
| import javax.xml.ws.soap.SOAPBinding; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| /** Utilities used throughout the Description package. */ |
| public class DescriptionUtils { |
| private static final Log log = LogFactory.getLog(DescriptionUtils.class); |
| |
| static boolean isEmpty(String string) { |
| return (string == null || "".equals(string)); |
| } |
| |
| static boolean isEmpty(QName qname) { |
| return qname == null || isEmpty(qname.getLocalPart()); |
| } |
| |
| /** @return Returns TRUE if we find just one WebMethod Annotation with exclude flag set to false */ |
| static boolean falseExclusionsExist(DescriptionBuilderComposite dbc) { |
| MethodDescriptionComposite mdc = null; |
| Iterator<MethodDescriptionComposite> iter = dbc.getMethodDescriptionsList().iterator(); |
| |
| while (iter.hasNext()) { |
| mdc = iter.next(); |
| |
| WebMethodAnnot wma = mdc.getWebMethodAnnot(); |
| if (wma != null) { |
| if (wma.exclude() == false) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gathers all MethodDescriptionCompsite's that contain a WebMethod Annotation with the exclude |
| * set to FALSE |
| * |
| * @return Returns List<MethodDescriptionComposite> |
| */ |
| static ArrayList<MethodDescriptionComposite> getMethodsWithFalseExclusions( |
| DescriptionBuilderComposite dbc) { |
| ArrayList<MethodDescriptionComposite> mdcList = new ArrayList<MethodDescriptionComposite>(); |
| Iterator<MethodDescriptionComposite> iter = dbc.getMethodDescriptionsList().iterator(); |
| |
| if (DescriptionUtils.falseExclusionsExist(dbc)) { |
| while (iter.hasNext()) { |
| MethodDescriptionComposite mdc = iter.next(); |
| if (mdc.getWebMethodAnnot() != null) { |
| if (mdc.getWebMethodAnnot().exclude() == false) { |
| mdc.setDeclaringClass(dbc.getClassName()); |
| mdcList.add(mdc); |
| } |
| } |
| } |
| } |
| |
| return mdcList; |
| } |
| |
| /* |
| * Check whether a MethodDescriptionComposite contains a WebMethod annotation with |
| * exlude set to true |
| */ |
| static boolean isExcludeTrue(MethodDescriptionComposite mdc) { |
| |
| if (mdc.getWebMethodAnnot() != null) { |
| if (mdc.getWebMethodAnnot().exclude() == true) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static String javifyClassName(String className) { |
| if (className.indexOf("/") != -1) { |
| return className.replaceAll("/", "."); |
| } |
| return className; |
| } |
| |
| /** |
| * Return the name of the class without any package qualifier. |
| * |
| * @param theClass |
| * @return the name of the class sans package qualification. |
| */ |
| static String getSimpleJavaClassName(String name) { |
| String returnName = null; |
| |
| if (name != null) { |
| String fqName = name; |
| |
| // We need the "simple name", so strip off any package information from the name |
| int endOfPackageIndex = fqName.lastIndexOf('.'); |
| int startOfClassIndex = endOfPackageIndex + 1; |
| returnName = fqName.substring(startOfClassIndex); |
| } |
| return returnName; |
| } |
| |
| /** |
| * Returns the package name from the class. If no package, then returns null |
| * |
| * @param theClassName |
| * @return |
| */ |
| static String getJavaPackageName(String theClassName) { |
| String returnPackage = null; |
| if (theClassName != null) { |
| String fqName = theClassName; |
| // Get the package name, if there is one |
| int endOfPackageIndex = fqName.lastIndexOf('.'); |
| if (endOfPackageIndex >= 0) { |
| returnPackage = fqName.substring(0, endOfPackageIndex); |
| } |
| } |
| return returnPackage; |
| } |
| |
| /** |
| * Create a JAX-WS namespace based on the package name |
| * |
| * @param packageName |
| * @param protocol |
| * @return |
| */ |
| static final String NO_PACKAGE_HOST_NAME = "DefaultNamespace"; |
| |
| static String makeNamespaceFromPackageName(String packageName, String protocol) { |
| if (DescriptionUtils.isEmpty(protocol)) { |
| protocol = "http"; |
| } |
| if (DescriptionUtils.isEmpty(packageName)) { |
| return protocol + "://" + NO_PACKAGE_HOST_NAME; |
| } |
| StringTokenizer st = new StringTokenizer(packageName, "."); |
| String[] words = new String[ st.countTokens() ]; |
| for (int i = 0; i < words.length; ++i) |
| words[i] = st.nextToken(); |
| |
| StringBuffer sb = new StringBuffer(80); |
| for (int i = words.length - 1; i >= 0; --i) { |
| String word = words[i]; |
| // seperate with dot |
| if (i != words.length - 1) |
| sb.append('.'); |
| sb.append(word); |
| } |
| return protocol + "://" + sb.toString() + "/"; |
| } |
| |
| /** |
| * Determines whether a method should have an OperationDescription created for it based on the |
| * name. This is a convenience method to allow us to exlude methods such as constructors. |
| * |
| * @param methodName |
| * @return |
| */ |
| static boolean createOperationDescription(String methodName) { |
| if (methodName.equals(CONSTRUCTOR_METHOD)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * This is a helper method that will open a stream to an @HandlerChain configuration file. |
| * |
| * @param configFile - The path to the file |
| * @param className - The class in which the annotation was declared. This is used in case the |
| * file path is relative. |
| * @param classLoader - ClassLoader used to load relative file paths. |
| * @return |
| */ |
| public static InputStream openHandlerConfigStream(String configFile, String className, |
| ClassLoader |
| classLoader) { |
| InputStream configStream = null; |
| URL configURL; |
| if (log.isDebugEnabled()) { |
| log.debug("Attempting to load @HandlerChain configuration file: " + configFile + |
| " relative to class: " + className); |
| } |
| try { |
| configURL = new URL(configFile); |
| if (configURL != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Found absolute @HandlerChain configuration file: " + configFile); |
| } |
| configStream = configURL.openStream(); |
| } |
| } |
| catch (MalformedURLException e) { |
| // try another method to obtain a stream to the configuration file |
| } |
| catch (IOException e) { |
| // report this since it was a valid URL but the openStream caused a problem |
| throw ExceptionFactory.makeWebServiceException(Messages.getMessage("hcConfigLoadFail", |
| configFile, className, |
| e.toString())); |
| } |
| if (configStream == null) { |
| if (log.isDebugEnabled()) { |
| log.debug("@HandlerChain.file attribute refers to a relative location: " |
| + configFile); |
| } |
| className = className.replace(".", "/"); |
| try { |
| if (log.isDebugEnabled()) { |
| log.debug("Resolving @HandlerChain configuration file: " + configFile + |
| " relative to class file: " + className); |
| } |
| URI uri = new URI(className); |
| uri = uri.resolve(configFile); |
| String resolvedPath = uri.toString(); |
| if (log.isDebugEnabled()) { |
| log.debug("@HandlerChain.file resolved file path location: " + resolvedPath); |
| } |
| configStream = getInputStream(resolvedPath, classLoader); |
| } |
| catch (URISyntaxException e) { |
| throw ExceptionFactory.makeWebServiceException(Messages.getMessage("hcConfigLoadFail", |
| configFile, className, |
| e.toString())); |
| } |
| } |
| if (configStream == null) { |
| //throw ExceptionFactory.makeWebServiceException(Messages.getMessage("handlerChainNS", |
| // configFile, className)); |
| // No longer throwing an exception here. This method is best-effort, and the caller may |
| // change the class to which the path is relative and try again. The caller is responsible |
| // for determining when to "give up" |
| if (log.isDebugEnabled()) { |
| log.debug("@HandlerChain configuration fail: " + configFile + " in class: " + className + " failed to load."); |
| } |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("@HandlerChain configuration file: " + configFile + " in class: " + |
| className + " was successfully loaded."); |
| } |
| } |
| return configStream; |
| } |
| |
| private static InputStream getInputStream(String path, ClassLoader classLoader) { |
| InputStream configStream = classLoader.getResourceAsStream(path); |
| if (configStream == null) { |
| // try another classloader |
| ClassLoader cl = System.class.getClassLoader(); |
| if (cl != null) { |
| configStream = cl.getResourceAsStream(path); |
| } |
| } |
| if (configStream == null) { |
| // and another classloader |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| if (cl != null) { |
| configStream = cl.getResourceAsStream(path); |
| } |
| } |
| return configStream; |
| } |
| |
| /** |
| * Determine is this method is an async method |
| * @param method - The method to examine |
| * @return |
| */ |
| public static boolean isAsync(Method method) { |
| |
| if (method == null) { |
| return false; |
| } |
| |
| String methodName = method.getName(); |
| Class returnType = method.getReturnType(); |
| |
| if (methodName.endsWith("Async") |
| && (returnType.isAssignableFrom(javax.xml.ws.Response.class) || returnType |
| .isAssignableFrom(java.util.concurrent.Future.class))) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public static HandlerChainsType loadHandlerChains(InputStream is, ClassLoader classLoader) { |
| try { |
| HandlerChainsParser parser = new HandlerChainsParser(); |
| return parser.loadHandlerChains(is); |
| } catch (Exception e) { |
| throw ExceptionFactory |
| .makeWebServiceException(Messages.getMessage("loadHandlerChainErr", e.getMessage())); |
| } |
| } |
| |
| |
| /** |
| * This method will loop through a list of extensibility elements looking for one |
| * of four objects: SOAPBody, SOAP12Body, SOAPHeader, SOAP12Header. If any of these |
| * objects are found the namespace URI from this object will be returned. |
| */ |
| public static String getNamespaceFromSOAPElement(List extElements) { |
| Iterator extIter = extElements.iterator(); |
| while (extIter.hasNext()) { |
| Object extObj = extIter.next(); |
| if (extObj instanceof SOAPBody) { |
| if (log.isDebugEnabled()) { |
| log.debug("Returning SOAPBody namespace: " |
| + ((SOAPBody) extObj).getNamespaceURI()); |
| } |
| return ((SOAPBody) extObj).getNamespaceURI(); |
| } else if (extObj instanceof SOAP12Body) { |
| if (log.isDebugEnabled()) { |
| log.debug("Returning SOAP12Body namespace: " |
| + ((SOAP12Body) extObj).getNamespaceURI()); |
| } |
| return ((SOAP12Body) extObj).getNamespaceURI(); |
| } else if (extObj instanceof SOAPHeader) { |
| if (log.isDebugEnabled()) { |
| log.debug("Returning SOAPHeader namespace: " |
| + ((SOAPHeader) extObj).getNamespaceURI()); |
| } |
| return ((SOAPHeader) extObj).getNamespaceURI(); |
| } else if (extObj instanceof SOAP12Header) { |
| if (log.isDebugEnabled()) { |
| log.debug("Returning SOAP12Header namespace: " |
| + ((SOAP12Header) extObj).getNamespaceURI()); |
| } |
| return ((SOAP12Header) extObj).getNamespaceURI(); |
| } |
| else if (extObj instanceof MIMEMultipartRelated) { |
| if (log.isDebugEnabled()) { |
| log.debug("Found a MIMEMultipartRelated element. Unwrapping to get SOAP binding."); |
| } |
| MIMEMultipartRelated mime = (MIMEMultipartRelated) extObj; |
| List mimeParts = mime.getMIMEParts(); |
| |
| Iterator itr = mimeParts.iterator(); |
| while (itr.hasNext()) { |
| MIMEPart mimePart = (MIMEPart) itr.next(); |
| List elements = mimePart.getExtensibilityElements(); |
| |
| String ns = getNamespaceFromSOAPElement(elements); |
| return ns; |
| } |
| } |
| } |
| return null; |
| } |
| /** |
| * This method will process a WSDL Binding and build AttachmentDescription objects if the |
| * WSDL dicatates attachments. |
| */ |
| public static void getAttachmentFromBinding(OperationDescriptionImpl opDesc, Binding binding) { |
| if (binding != null) { |
| Iterator bindingOpIter = binding.getBindingOperations().iterator(); |
| while (bindingOpIter.hasNext()) { |
| BindingOperation bindingOp = (BindingOperation) bindingOpIter.next(); |
| // found the BindingOperation that matches the current OperationDescription |
| if (bindingOp.getName().equals(opDesc.getName().getLocalPart())) { |
| if (bindingOp.getBindingInput() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Processing binding opertion input"); |
| } |
| processBindingForMIME(bindingOp.getBindingInput().getExtensibilityElements(), |
| opDesc, bindingOp.getOperation(), true); |
| } |
| if (bindingOp.getBindingOutput() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Processing binding output"); |
| } |
| processBindingForMIME(bindingOp.getBindingOutput().getExtensibilityElements(), |
| opDesc, bindingOp.getOperation(), false); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will loop through the extensibility elements for a given BindingInput or |
| * BindingOutput element and determine if it has any MIMEMultipartRelated content. If it |
| * does it will build up the appropriate AttachmentDescription objects. |
| */ |
| private static void processBindingForMIME(List extensibilityElements, |
| OperationDescriptionImpl opDesc, |
| Operation operation, |
| boolean isRequest) { |
| Iterator extensibilityIter = extensibilityElements.iterator(); |
| while (extensibilityIter.hasNext()) { |
| Object obj = extensibilityIter.next(); |
| if (obj instanceof MIMEMultipartRelated) { |
| if (log.isDebugEnabled()) { |
| log.debug("Found a mime:multipartRelated extensiblity element."); |
| } |
| // Found mime information now process it and determine if we need to |
| // create an AttachmentDescription |
| MIMEMultipartRelated mime = (MIMEMultipartRelated) obj; |
| Iterator partIter = mime.getMIMEParts().iterator(); |
| while (partIter.hasNext()) { |
| if (log.isDebugEnabled()) { |
| log.debug("Found a mime:part child element."); |
| } |
| MIMEPart mimePart = (MIMEPart) partIter.next(); |
| Iterator mExtIter = mimePart.getExtensibilityElements().iterator(); |
| // Process each mime part to determine if there is mime content |
| while (mExtIter.hasNext()) { |
| Object obj2 = mExtIter.next(); |
| // For mime content we need to potentially create an AttachmentDescription |
| if (obj2 instanceof MIMEContent) { |
| MIMEContent mimeContent = (MIMEContent) obj2; |
| String part = mimeContent.getPart(); |
| String type = mimeContent.getType(); |
| // if we have not already processed this part for the operation |
| if (opDesc.getPartAttachmentDescription(part) == null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Adding new AttachmentDescription for part: " + part |
| + " on operation: " + opDesc.getOperationName()); |
| } |
| AttachmentDescription attachmentDesc = |
| new AttachmentDescriptionImpl(AttachmentType.SWA, |
| new String[] { type }); |
| opDesc.addPartAttachmentDescription(part, attachmentDesc); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Already created AttachmentDescription for part: " |
| + part + " of type: " + type); |
| } |
| } |
| } |
| else if (obj2 instanceof SOAPBody || obj2 instanceof SOAP12Body) { |
| if (log.isDebugEnabled()) { |
| log.debug("Found a body element with potential nested mime content"); |
| } |
| |
| // Flag whether there's a potential nested attachment. |
| if (isRequest) { |
| opDesc.setHasRequestSwaRefAttachments(true); |
| } |
| else { |
| opDesc.setHasResponseSwaRefAttachments(true); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public static void registerHandlerHeaders(AxisService axisService, List<Handler> handlers) { |
| if (handlers == null || axisService == null) { |
| return; |
| } |
| |
| ArrayList<QName> understoodHeaderQNames = new ArrayList<QName>(); |
| for (Handler handler : handlers) { |
| if (handler instanceof SOAPHandler) { |
| SOAPHandler soapHandler = (SOAPHandler) handler; |
| |
| Set<QName> headers = soapHandler.getHeaders(); |
| if (headers != null) { |
| for (QName header : headers) { |
| if (!understoodHeaderQNames.contains(header)) { |
| understoodHeaderQNames.add(header); |
| } |
| } |
| } |
| } |
| } |
| |
| if (!understoodHeaderQNames.isEmpty()) { |
| Parameter headerQNParameter = |
| new Parameter(EndpointDescription.HANDLER_PARAMETER_QNAMES, understoodHeaderQNames); |
| try { |
| axisService.addParameter(headerQNParameter); |
| } catch (AxisFault e) { |
| log.warn(Messages.getMessage("regHandlerHeadersErr",axisService.getName(),e.getMessage())); |
| } |
| } |
| } |
| |
| /** |
| * Given a binding type value based on a JAXWS anntation, return the corresponding WSDL |
| * binding type. The JAXWS annotation values understood are those returned by |
| * mapBindingTypeWsdltoAnnotation. |
| * |
| * @see #mapBindingTypeWsdlToAnnotation(String, String) |
| * |
| * @param annotationBindingType The binding type as represented by a JAXWS annotation value |
| * @return The binding type as represented by a WSDL binding extension namespace value |
| */ |
| public static String mapBindingTypeAnnotationToWsdl(String annotationBindingType) { |
| String wsdlBindingType = null; |
| |
| if (SOAPBinding.SOAP11HTTP_BINDING.equals(annotationBindingType) |
| || MDQConstants.SOAP11JMS_BINDING.equals(annotationBindingType)) { |
| wsdlBindingType = EndpointDescriptionWSDL.SOAP11_WSDL_BINDING; |
| } else if (SOAPBinding.SOAP12HTTP_BINDING.equals(annotationBindingType) |
| || MDQConstants.SOAP12JMS_BINDING.equals(annotationBindingType)) { |
| wsdlBindingType = EndpointDescriptionWSDL.SOAP12_WSDL_BINDING; |
| } else if (javax.xml.ws.http.HTTPBinding.HTTP_BINDING.equals(annotationBindingType)) { |
| wsdlBindingType = EndpointDescriptionWSDL.HTTP_WSDL_BINDING; |
| } |
| |
| return wsdlBindingType; |
| } |
| |
| /** |
| * Given a binding type value based on WSDL, return the corresponding JAXWS annotation value. |
| * The WSDL binding type values are based on the namespace of the binding extension element. |
| * The JAXWS annotation values correspond to the values to the HTTPBinding and SOAPBinding |
| * annotations. Additionally, proprietary values for JMS bindings are supported. The JAXWS |
| * binding type annotation values returned could be from SOAPBinding or HTTPBinding. |
| * |
| * @param wsdlBindingType The binding type as represnted by the WSDL binding extension namespace |
| * @param soapTransport The WSDL transport. Used to determine if a JMS binding type should |
| * be returned |
| * @return The binding represented by a JAXWS Binding Type Annotation value from either |
| * SOAPBinding or HTTPBinding. |
| */ |
| public static String mapBindingTypeWsdlToAnnotation(String wsdlBindingType, String soapTransport) { |
| String soapBindingType = null; |
| if (EndpointDescriptionWSDL.SOAP11_WSDL_BINDING.equals(wsdlBindingType)) { |
| if (MDQConstants.SOAP11JMS_BINDING.equals(soapTransport)) { |
| soapBindingType = MDQConstants.SOAP11JMS_BINDING; |
| } else { |
| //REVIEW: We are making the assumption that if not JMS, then HTTP |
| soapBindingType = SOAPBinding.SOAP11HTTP_BINDING; |
| } |
| } else if (EndpointDescriptionWSDL.SOAP12_WSDL_BINDING.equals(wsdlBindingType)) { |
| if (MDQConstants.SOAP12JMS_BINDING.equals(soapTransport)) { |
| soapBindingType = MDQConstants.SOAP12JMS_BINDING; |
| } else { |
| //REVIEW: We are making the assumption that if not JMS, then HTTP |
| soapBindingType = SOAPBinding.SOAP12HTTP_BINDING; |
| } |
| } else if (EndpointDescriptionWSDL.HTTP_WSDL_BINDING.equals(wsdlBindingType)) { |
| soapBindingType = javax.xml.ws.http.HTTPBinding.HTTP_BINDING; |
| } |
| return soapBindingType; |
| } |
| } |