/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ws.metadata.builder.jaxws;
// $Id: JAXWSMetaDataBuilder.java 6998 2008-05-09 21:33:49Z darran.lofthouse@jboss.com $
import com.sun.xml.bind.api.JAXBRIContext;
import com.sun.xml.bind.api.TypeReference;
import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.jaxws.DynamicWrapperGenerator;
import org.jboss.ws.core.jaxws.JAXBContextFactory;
import org.jboss.ws.core.jaxws.WrapperGenerator;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.core.soap.Use;
import org.jboss.ws.core.utils.HolderUtils;
import org.jboss.ws.core.utils.JBossWSEntityResolver;
import org.jboss.ws.extensions.addressing.AddressingPropertiesImpl;
import org.jboss.ws.extensions.addressing.metadata.AddressingOpMetaExt;
import org.jboss.ws.extensions.xop.jaxws.AttachmentScanResult;
import org.jboss.ws.extensions.xop.jaxws.ReflectiveAttachmentRefScanner;
import org.jboss.ws.metadata.accessor.JAXBAccessor;
import org.jboss.ws.metadata.accessor.JAXBAccessorFactoryCreator;
import org.jboss.ws.metadata.builder.MetaDataBuilder;
import org.jboss.ws.metadata.umdm.EndpointMetaData;
import org.jboss.ws.metadata.umdm.FaultMetaData;
import org.jboss.ws.metadata.umdm.HandlerMetaDataJAXWS;
import org.jboss.ws.metadata.umdm.OperationMetaData;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.ws.metadata.umdm.ServerEndpointMetaData;
import org.jboss.ws.metadata.umdm.TypeMappingMetaData;
import org.jboss.ws.metadata.umdm.TypesMetaData;
import org.jboss.ws.metadata.umdm.WrappedParameter;
import org.jboss.ws.metadata.wsdl.WSDLBinding;
import org.jboss.ws.metadata.wsdl.WSDLBindingMessageReference;
import org.jboss.ws.metadata.wsdl.WSDLBindingOperation;
import org.jboss.ws.metadata.wsdl.WSDLDefinitions;
import org.jboss.ws.metadata.wsdl.WSDLMIMEPart;
import org.jboss.wsf.common.JavaUtils;
import org.jboss.wsf.spi.binding.BindingCustomization;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.HandlerChainsObjectFactory;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedHandlerChainMetaData;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedHandlerChainsMetaData;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedHandlerMetaData;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedHandlerMetaData.HandlerType;
import org.jboss.wsf.spi.deployment.Endpoint;
import org.jboss.xb.binding.ObjectModelFactory;
import org.jboss.xb.binding.Unmarshaller;
import org.jboss.xb.binding.UnmarshallerFactory;
import javax.jws.HandlerChain;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPMessageHandlers;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import javax.xml.ws.BindingType;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
import javax.xml.ws.WebFault;
import javax.xml.ws.addressing.Action;
import javax.xml.ws.addressing.AddressingProperties;
import java.io.File;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Abstract class that represents a JAX-WS metadata builder.
*
* @author <a href="mailto:jason.greene@jboss.com">Jason T. Greene</a>
* @author Thomas.Diesler@jboss.com
*/
@SuppressWarnings("deprecation")
public class JAXWSMetaDataBuilder extends MetaDataBuilder
{
protected static final Logger log = Logger.getLogger(JAXWSWebServiceMetaDataBuilder.class);
protected List<Class> javaTypes = new ArrayList<Class>();
protected JAXBRIContext jaxbCtx;
protected List<TypeReference> typeRefs = new ArrayList<TypeReference>();
protected WrapperGenerator wrapperGenerator;
protected void processBindingType(EndpointMetaData epMetaData, Class<?> wsClass)
{
if (wsClass.isAnnotationPresent(BindingType.class))
{
log.debug("processBindingType on: " + wsClass.getName());
BindingType anBindingType = (BindingType)wsClass.getAnnotation(BindingType.class);
epMetaData.setBindingId(anBindingType.value());
}
}
protected void processSOAPBinding(EndpointMetaData epMetaData, Class<?> wsClass)
{
if (wsClass.isAnnotationPresent(SOAPBinding.class))
{
log.debug("processSOAPBinding on: " + wsClass.getName());
SOAPBinding anSoapBinding = wsClass.getAnnotation(SOAPBinding.class);
SOAPBinding.Style attrStyle = anSoapBinding.style();
Style style = (attrStyle == SOAPBinding.Style.RPC ? Style.RPC : Style.DOCUMENT);
epMetaData.setStyle(style);
SOAPBinding.Use attrUse = anSoapBinding.use();
if (attrUse == SOAPBinding.Use.ENCODED)
throw new WSException("SOAP encoding is not supported for JSR-181 deployments");
epMetaData.setEncodingStyle(Use.LITERAL);
ParameterStyle paramStyle = anSoapBinding.parameterStyle();
epMetaData.setParameterStyle(paramStyle);
}
}
/**
* Process an optional @HandlerChain annotation
*
* Location of the handler chain file. The location supports 2 formats.
*
* 1. An absolute java.net.URL in externalForm.
* (ex: http://myhandlers.foo.com/handlerfile1.xml)
*
* 2. A relative path from the source file or class file.
* (ex: bar/handlerfile1.xml)
*/
protected void processHandlerChain(EndpointMetaData epMetaData, Class<?> wsClass)
{
if (wsClass.isAnnotationPresent(SOAPMessageHandlers.class))
throw new WSException("Cannot combine @HandlerChain with @SOAPMessageHandlers");
if (wsClass.isAnnotationPresent(HandlerChain.class))
{
HandlerChain anHandlerChain = wsClass.getAnnotation(HandlerChain.class);
String filename = anHandlerChain.file();
// Setup the endpoint handlers
UnifiedHandlerChainsMetaData handlerChainsMetaData = getHandlerChainsMetaData(wsClass, filename);
for (UnifiedHandlerChainMetaData UnifiedHandlerChainMetaData : handlerChainsMetaData.getHandlerChains())
{
for (UnifiedHandlerMetaData uhmd : UnifiedHandlerChainMetaData.getHandlers())
{
HandlerMetaDataJAXWS hmd = HandlerMetaDataJAXWS.newInstance(uhmd, HandlerType.ENDPOINT);
epMetaData.addHandler(hmd);
}
}
}
}
public static UnifiedHandlerChainsMetaData getHandlerChainsMetaData(Class<?> wsClass, String filename)
{
URL fileURL = null;
log.debug("processHandlerChain [" + filename + "] on: " + wsClass.getName());
// Try the filename as URL
try
{
fileURL = new URL(filename);
}
catch (MalformedURLException ex)
{
// ignore
}
// Try the filename as File
if (fileURL == null)
{
try
{
File file = new File(filename);
if (file.exists())
fileURL = file.toURL();
}
catch (MalformedURLException e)
{
// ignore
}
}
// Try the filename as Resource
if (fileURL == null)
{
log.debug(wsClass.getProtectionDomain().getCodeSource());
log.debug(wsClass.getClassLoader());
fileURL = wsClass.getClassLoader().getResource(filename);
}
// Try the filename relative to class
if (fileURL == null)
{
String filepath = filename;
String packagePath = wsClass.getPackage().getName().replace('.', '/');
String resourcePath = packagePath + "/" + filepath;
while (filepath.startsWith("../"))
{
packagePath = packagePath.substring(0, packagePath.lastIndexOf("/"));
filepath = filepath.substring(3);
resourcePath = packagePath + "/" + filepath;
}
fileURL = wsClass.getClassLoader().getResource(resourcePath);
}
if (fileURL == null)
throw new WSException("Cannot resolve handler file '" + filename + "' on " + wsClass.getName());
log.debug("Loading handler chain: " + fileURL);
UnifiedHandlerChainsMetaData handlerChainsMetaData = null;
try
{
InputStream is = fileURL.openStream();
try
{
Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
unmarshaller.setValidation(true);
unmarshaller.setSchemaValidation(true);
unmarshaller.setEntityResolver(new JBossWSEntityResolver());
ObjectModelFactory factory = new HandlerChainsObjectFactory();
handlerChainsMetaData = (UnifiedHandlerChainsMetaData)unmarshaller.unmarshal(is, factory, null);
}
finally
{
is.close();
}
}
catch (RuntimeException rte)
{
throw rte;
}
catch (Exception ex)
{
throw new WSException("Cannot process handler chain: " + filename, ex);
}
return handlerChainsMetaData;
}
private void addFault(OperationMetaData opMetaData, Class<?> exception)
{
if (opMetaData.isOneWay())
throw new IllegalStateException("JSR-181 4.3.1 - A JSR-181 processor is REQUIRED to report an error if an operation marked "
+ "@Oneway has a return value, declares any checked exceptions or has any INOUT or OUT parameters.");
WebFault anWebFault = exception.getAnnotation(WebFault.class);
// Only the element name is effected by @WebFault, the type uses the same convention
QName xmlType = new QName(opMetaData.getQName().getNamespaceURI(), exception.getSimpleName());
String name = xmlType.getLocalPart();
String namespace = xmlType.getNamespaceURI();
String faultBean = null;
Class faultBeanClass = getFaultInfo(exception);
if (faultBeanClass != null)
faultBean = faultBeanClass.getName();
/*
* If @WebFault is present, and the exception contains getFaultInfo, the
* return value should be used. Otherwise we need to generate the bean.
*/
if (anWebFault != null)
{
if (anWebFault.name().length() > 0)
name = anWebFault.name();
if (anWebFault.targetNamespace().length() > 0)
namespace = anWebFault.targetNamespace();
if (anWebFault.faultBean().length() > 0)
faultBean = anWebFault.faultBean();
}
if (faultBean == null)
faultBean = JavaUtils.getPackageName(opMetaData.getEndpointMetaData().getServiceEndpointInterface()) + ".jaxws." + exception.getSimpleName() + "Bean";
QName xmlName = new QName(namespace, name);
FaultMetaData fmd = new FaultMetaData(opMetaData, xmlName, xmlType, exception.getName());
fmd.setFaultBeanName(faultBean);
if (fmd.loadFaultBean() == null)
wrapperGenerator.generate(fmd);
javaTypes.add(fmd.getFaultBean());
typeRefs.add(new TypeReference(fmd.getXmlName(), fmd.getFaultBean()));
opMetaData.addFault(fmd);
}
private String convertToVariable(String localName)
{
return JAXBRIContext.mangleNameToVariableName(localName.intern());
}
private String[] convertTypeArguments(Class rawType, Type type)
{
if (!Collection.class.isAssignableFrom(rawType) && !Map.class.isAssignableFrom(rawType))
return null;
if (!(type instanceof ParameterizedType))
return null;
ParameterizedType paramType = (ParameterizedType)type;
Type[] arguments = paramType.getActualTypeArguments();
String[] ret = new String[arguments.length];
for (int i = 0; i < arguments.length; i++)
ret[i] = JavaUtils.erasure(arguments[i]).getName();
return ret;
}
private ParameterMetaData createRequestWrapper(OperationMetaData operation, Method method)
{
String requestWrapperType = null;
QName xmlName = operation.getQName();
QName xmlType = xmlName;
if (method.isAnnotationPresent(RequestWrapper.class))
{
RequestWrapper anReqWrapper = method.getAnnotation(RequestWrapper.class);
String localName = anReqWrapper.localName().length() > 0 ? anReqWrapper.localName() : xmlName.getLocalPart();
String targetNamespace = anReqWrapper.targetNamespace().length() > 0 ? anReqWrapper.targetNamespace() : xmlName.getNamespaceURI();
xmlName = new QName(targetNamespace, localName);
if (anReqWrapper.className().length() > 0)
requestWrapperType = anReqWrapper.className();
}
// Conformance 3.18, the default value must be the same as the method name
if (requestWrapperType == null)
{
String packageName = JavaUtils.getPackageName(method.getDeclaringClass());
requestWrapperType = packageName + ".jaxws." + JavaUtils.capitalize(method.getName());
}
// JAX-WS p.37 pg.1, the annotation only affects the element name, not the type name
ParameterMetaData wrapperParameter = new ParameterMetaData(operation, xmlName, xmlType, requestWrapperType);
wrapperParameter.setAccessorFactoryCreator(new JAXBAccessorFactoryCreator());
operation.addParameter(wrapperParameter);
return wrapperParameter;
}
private ParameterMetaData createResponseWrapper(OperationMetaData operation, Method method)
{
QName operationQName = operation.getQName();
QName xmlName = new QName(operationQName.getNamespaceURI(), operationQName.getLocalPart() + "Response");
QName xmlType = xmlName;
String responseWrapperType = null;
if (method.isAnnotationPresent(ResponseWrapper.class))
{
ResponseWrapper anResWrapper = method.getAnnotation(ResponseWrapper.class);
String localName = anResWrapper.localName().length() > 0 ? anResWrapper.localName() : xmlName.getLocalPart();
String targetNamespace = anResWrapper.targetNamespace().length() > 0 ? anResWrapper.targetNamespace() : xmlName.getNamespaceURI();
xmlName = new QName(targetNamespace, localName);
if (anResWrapper.className().length() > 0)
responseWrapperType = anResWrapper.className();
}
if (responseWrapperType == null)
{
String packageName = JavaUtils.getPackageName(method.getDeclaringClass());
responseWrapperType = packageName + ".jaxws." + JavaUtils.capitalize(method.getName()) + "Response";
}
ParameterMetaData retMetaData = new ParameterMetaData(operation, xmlName, xmlType, responseWrapperType);
retMetaData.setAccessorFactoryCreator(new JAXBAccessorFactoryCreator());
operation.setReturnParameter(retMetaData);
return retMetaData;
}
private Class getFaultInfo(Class exception)
{
try
{
Method method = exception.getMethod("getFaultInfo");
Class returnType = method.getReturnType();
if (returnType == void.class)
return null;
return returnType;
}
catch (SecurityException e)
{
throw new WSException("Unexpected security exception: " + e.getMessage(), e);
}
catch (NoSuchMethodException e)
{
return null;
}
}
private ParameterMode getParameterMode(WebParam anWebParam, Class javaType)
{
if (anWebParam != null)
{
if (anWebParam.mode() == WebParam.Mode.INOUT)
return ParameterMode.INOUT;
if (anWebParam.mode() == WebParam.Mode.OUT)
return ParameterMode.OUT;
}
return HolderUtils.isHolderType(javaType) ? ParameterMode.INOUT : ParameterMode.IN;
}
private WebParam getWebParamAnnotation(Method method, int pos)
{
for (Annotation annotation : method.getParameterAnnotations()[pos])
if (annotation instanceof WebParam)
return (WebParam)annotation;
return null;
}
private QName getWebParamName(OperationMetaData opMetaData, int index, WebParam webParam)
{
String namespace = null;
String name = null;
boolean header = false;
if (webParam != null)
{
if (webParam.targetNamespace().length() > 0)
namespace = webParam.targetNamespace();
// RPC types use the partName for their XML name
if (webParam.partName().length() > 0 && opMetaData.isRPCLiteral())
name = webParam.partName();
else if (webParam.name().length() > 0)
name = webParam.name();
header = webParam.header();
}
// Bare and headers must be qualified
if (namespace == null && (opMetaData.isDocumentBare() || header))
namespace = opMetaData.getQName().getNamespaceURI();
// RPC body parts must have no namespace
else if (opMetaData.isRPCLiteral() && !header)
namespace = null;
// Bare uses the operation name as the default, everything else is generated
if (name == null)
name = opMetaData.isDocumentBare() && !header ? opMetaData.getQName().getLocalPart() : "arg" + index;
return (namespace != null) ? new QName(namespace, name) : new QName(name);
}
private QName getWebResultName(OperationMetaData opMetaData, WebResult anWebResult)
{
String name = null;
String namespace = null;
boolean header = false;
if (anWebResult != null)
{
if (anWebResult.targetNamespace().length() > 0)
namespace = anWebResult.targetNamespace();
// RPC types use the partName for their XML name
if (anWebResult.partName().length() > 0 && opMetaData.isRPCLiteral())
name = anWebResult.partName();
else if (anWebResult.name().length() > 0)
name = anWebResult.name();
header = anWebResult.header();
}
// Bare and headers must be qualified
if (namespace == null && (opMetaData.isDocumentBare() || header))
namespace = opMetaData.getQName().getNamespaceURI();
// RPC body parts must have no namespace
else if (opMetaData.isRPCLiteral() && !header)
namespace = null;
// Bare uses the operation name as the default, everything else is generated
if (name == null)
name = opMetaData.isDocumentBare() && !header ? opMetaData.getResponseName().getLocalPart() : "return";
return (namespace != null) ? new QName(namespace, name) : new QName(name);
}
/**
* Process operation meta data extensions.
*/
private void processMetaExtensions(Method method, EndpointMetaData epMetaData, OperationMetaData opMetaData)
{
AddressingProperties ADDR = new AddressingPropertiesImpl();
AddressingOpMetaExt addrExt = new AddressingOpMetaExt(ADDR.getNamespaceURI());
Action anAction = method.getAnnotation(Action.class);
if (anAction != null)
{
addrExt.setInboundAction(anAction.input());
addrExt.setOutboundAction(anAction.output());
}
else
// default action values
{
// TODO: figure out a way to assign message name instead of IN and OUT
String tns = epMetaData.getPortName().getNamespaceURI();
String portTypeName = epMetaData.getPortName().getLocalPart();
addrExt.setInboundAction(tns + "/" + portTypeName + "/IN");
if (!opMetaData.isOneWay())
addrExt.setOutboundAction(tns + "/" + portTypeName + "/OUT");
}
opMetaData.addExtension(addrExt);
}
private void processWebMethod(EndpointMetaData epMetaData, Method method)
{
String javaName = method.getName();
// skip asnyc methods, they dont need meta data representation
if (method.getName().endsWith(Constants.ASYNC_METHOD_SUFFIX))
return;
// reflection defaults
String soapAction = "";
String operationName = method.getName();
// annotation values that override defaults
if (method.isAnnotationPresent(WebMethod.class))
{
WebMethod anWebMethod = method.getAnnotation(WebMethod.class);
soapAction = anWebMethod.action();
if (anWebMethod.operationName().length() > 0)
{
operationName = anWebMethod.operationName();
}
}
String targetNS = epMetaData.getPortTypeName().getNamespaceURI();
OperationMetaData opMetaData = new OperationMetaData(epMetaData, new QName(targetNS, operationName), javaName);
opMetaData.setOneWay(method.isAnnotationPresent(Oneway.class));
opMetaData.setSOAPAction(soapAction);
if (method.isAnnotationPresent(SOAPBinding.class))
{
SOAPBinding anBinding = method.getAnnotation(SOAPBinding.class);
if (anBinding.style() != SOAPBinding.Style.DOCUMENT || epMetaData.getStyle() != Style.DOCUMENT)
throw new IllegalArgumentException("@SOAPBinding must be specified using DOCUMENT style when placed on a method");
opMetaData.setParameterStyle(anBinding.parameterStyle());
}
epMetaData.addOperation(opMetaData);
// Build parameter meta data
// Attachment annotations on SEI parameters
List<AttachmentScanResult> scanResult = ReflectiveAttachmentRefScanner.scanMethod(method);
Class[] parameterTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
ParameterMetaData wrapperParameter = null, wrapperOutputParameter = null;
List<WrappedParameter> wrappedParameters = null, wrappedOutputParameters = null;
// Force paramter style to wrapped
if (method.isAnnotationPresent(RequestWrapper.class) || method.isAnnotationPresent(ResponseWrapper.class))
{
epMetaData.setParameterStyle(ParameterStyle.WRAPPED);
}
if (opMetaData.isDocumentWrapped())
{
wrapperParameter = createRequestWrapper(opMetaData, method);
wrappedParameters = new ArrayList<WrappedParameter>(parameterTypes.length);
wrapperParameter.setWrappedParameters(wrappedParameters);
if (!opMetaData.isOneWay())
{
wrapperOutputParameter = createResponseWrapper(opMetaData, method);
wrappedOutputParameters = new ArrayList<WrappedParameter>(parameterTypes.length + 1);
wrapperOutputParameter.setWrappedParameters(wrappedOutputParameters);
}
}
for (int i = 0; i < parameterTypes.length; i++)
{
Class javaType = parameterTypes[i];
Type genericType = genericTypes[i];
String javaTypeName = javaType.getName();
WebParam anWebParam = getWebParamAnnotation(method, i);
boolean isHeader = anWebParam != null && anWebParam.header();
boolean isWrapped = opMetaData.isDocumentWrapped() && !isHeader;
ParameterMode mode = getParameterMode(anWebParam, javaType);
// Assert one-way
if (opMetaData.isOneWay() && mode != ParameterMode.IN)
throw new IllegalArgumentException("A one-way operation can not have output parameters [" + "method = " + method.getName() + ", parameter = " + i + "]");
if (HolderUtils.isHolderType(javaType))
{
genericType = HolderUtils.getGenericValueType(genericType);
javaType = JavaUtils.erasure(genericType);
javaTypeName = javaType.getName();
}
if (isWrapped)
{
QName wrappedElementName = getWebParamName(opMetaData, i, anWebParam);
String variable = convertToVariable(wrappedElementName.getLocalPart());
WrappedParameter wrappedParameter = new WrappedParameter(wrappedElementName, javaTypeName, variable, i);
wrappedParameter.setTypeArguments(convertTypeArguments(javaType, genericType));
if (mode != ParameterMode.OUT)
wrappedParameters.add(wrappedParameter);
if (mode != ParameterMode.IN)
{
wrappedParameter.setHolder(true);
// WrappedParameters can not be shared between request/response objects (accessors)
if (mode == ParameterMode.INOUT)
wrappedParameter = new WrappedParameter(wrappedParameter);
wrappedOutputParameters.add(wrappedParameter);
}
processAttachmentAnnotationsWrapped(scanResult, i, wrappedParameter);
}
else
{
QName xmlName = getWebParamName(opMetaData, i, anWebParam);
ParameterMetaData paramMetaData = new ParameterMetaData(opMetaData, xmlName, javaTypeName);
paramMetaData.setInHeader(isHeader);
paramMetaData.setIndex(i);
paramMetaData.setMode(mode);
/*
* Note: The TCK enforces the following rule in the spec regarding
* partName: "This is only used if the operation is rpc style or if
* the operation is document style and the parameter style is BARE."
*
* This seems to be a flaw in the spec, because the intention is
* obviously to prevent the ambiguity of wrapped parameters that
* specify different partName values. There is, however, no reason
* that this limitation should apply to header parameters since they
* are never wrapped. In order to comply we adhere to this confusing
* rule, although I will ask for clarification.
*/
if (anWebParam != null && !opMetaData.isDocumentWrapped() && anWebParam.partName().length() > 0)
paramMetaData.setPartName(anWebParam.partName());
opMetaData.addParameter(paramMetaData);
javaTypes.add(javaType);
typeRefs.add(new TypeReference(xmlName, genericType, parameterAnnotations[i]));
processAttachmentAnnotations(scanResult, i, paramMetaData);
processMIMEBinding(epMetaData, opMetaData, paramMetaData);
}
}
// Build result meta data
Class returnType = method.getReturnType();
Type genericReturnType = method.getGenericReturnType();
String returnTypeName = returnType.getName();
if (!(returnType == void.class))
{
if (opMetaData.isOneWay())
throw new IllegalArgumentException("[JSR-181 2.5.1] The method '" + method.getName() + "' can not have a return value if it is marked OneWay");
WebResult anWebResult = method.getAnnotation(WebResult.class);
boolean isHeader = anWebResult != null && anWebResult.header();
boolean isWrappedBody = opMetaData.isDocumentWrapped() && !isHeader;
QName xmlName = getWebResultName(opMetaData, anWebResult);
if (isWrappedBody)
{
WrappedParameter wrapped = new WrappedParameter(xmlName, returnTypeName, convertToVariable(xmlName.getLocalPart()), -1);
wrapped.setTypeArguments(convertTypeArguments(returnType, genericReturnType));
// insert at the beginning just for prettiness
wrappedOutputParameters.add(0, wrapped);
processAttachmentAnnotationsWrapped(scanResult, -1, wrapped);
}
else
{
ParameterMetaData retMetaData = new ParameterMetaData(opMetaData, xmlName, returnTypeName);
retMetaData.setInHeader(isHeader);
retMetaData.setIndex(-1);
retMetaData.setMode(ParameterMode.OUT);
// Special case: If we have a document/literal wrapped message, then
// the return metadata must be the wrapper type that is sent in the
// body. So, in order to handle headers that are mapped to the java
// return value, we have to add them to a parameter with an index of
// -1 to signify the return value. All other binding styles use the
// expected return value mechanism.
if (opMetaData.isDocumentWrapped())
{
opMetaData.addParameter(retMetaData);
}
else
{
// See above comment in the parameter for loop section as to why
// we prevent customization of part names on document wrapped
// header parameters.
if (anWebResult != null && anWebResult.partName().length() > 0)
retMetaData.setPartName(anWebResult.partName());
opMetaData.setReturnParameter(retMetaData);
}
javaTypes.add(returnType);
typeRefs.add(new TypeReference(xmlName, genericReturnType, method.getAnnotations()));
processAttachmentAnnotations(scanResult, -1, retMetaData);
processMIMEBinding(epMetaData, opMetaData, retMetaData);
}
}
// Generate wrapper beans
if (opMetaData.isDocumentWrapped())
{
if (wrapperParameter.loadWrapperBean() == null)
wrapperGenerator.generate(wrapperParameter);
Class wrapperClass = wrapperParameter.getJavaType();
javaTypes.add(wrapperClass);
// In case there is no @XmlRootElement
typeRefs.add(new TypeReference(wrapperParameter.getXmlName(), wrapperClass));
if (!opMetaData.isOneWay())
{
if (wrapperOutputParameter.loadWrapperBean() == null)
wrapperGenerator.generate(wrapperOutputParameter);
wrapperClass = wrapperOutputParameter.getJavaType();
javaTypes.add(wrapperClass);
// In case there is no @XmlRootElement
typeRefs.add(new TypeReference(wrapperOutputParameter.getXmlName(), wrapperClass));
}
}
// Add faults
for (Class exClass : method.getExceptionTypes())
if (!RemoteException.class.isAssignableFrom(exClass))
addFault(opMetaData, exClass);
// process operation meta data extension
processMetaExtensions(method, epMetaData, opMetaData);
}
/**
* @see org.jboss.ws.metadata.builder.jaxws.JAXWSMetaDataBuilder#processAttachmentAnnotations(java.util.List, int, org.jboss.ws.metadata.umdm.ParameterMetaData)
* @param scanResult
* @param i
* @param wrappedParameter
*/
private void processAttachmentAnnotationsWrapped(List<AttachmentScanResult> scanResult, int i, WrappedParameter wrappedParameter)
{
AttachmentScanResult asr = ReflectiveAttachmentRefScanner.getResultByIndex(scanResult, i);
if (asr != null)
{
if (AttachmentScanResult.Type.SWA_REF == asr.getType())
wrappedParameter.setSwaRef(true);
else wrappedParameter.setXOP(true);
}
}
/**
* Update PMD according to attachment annotations that might be in place
* @param scanResult
* @param i
* @param parameter
*/
private void processAttachmentAnnotations(List<AttachmentScanResult> scanResult, int i, ParameterMetaData parameter)
{
AttachmentScanResult asr = ReflectiveAttachmentRefScanner.getResultByIndex(scanResult, i);
if (asr != null)
{
if (AttachmentScanResult.Type.SWA_REF == asr.getType())
parameter.setSwaRef(true);
else parameter.setXOP(true);
}
}
private void processMIMEBinding(EndpointMetaData epMetaData, OperationMetaData opMetaData, ParameterMetaData paramMetaData)
{
// process SWA metadata
WSDLDefinitions wsdlDef = epMetaData.getServiceMetaData().getWsdlDefinitions();
if (wsdlDef != null)
{
for (WSDLBinding binding : wsdlDef.getBindings())
{
for (WSDLBindingOperation bindingOp : binding.getOperations())
{
// it might an input or output parameter
WSDLBindingMessageReference[] inOrOutPut = (paramMetaData.getMode().equals(ParameterMode.IN) || paramMetaData.getMode().equals(ParameterMode.INOUT)) ? (WSDLBindingMessageReference[])bindingOp
.getInputs()
: (WSDLBindingMessageReference[])bindingOp.getOutputs();
if (inOrOutPut.length > 0)
{
// find matching operation
if (bindingOp.getRef().equals(opMetaData.getQName()))
{
WSDLBindingMessageReference bindingInput = inOrOutPut[0];
for (WSDLMIMEPart mimePart : bindingInput.getMimeParts())
{
String partName = mimePart.getPartName();
if (paramMetaData.getPartName().equals(partName))
{
log.debug("Identified 'mime:content' binding: " + partName + ", mimeTypes=" + mimePart.getMimeTypes());
paramMetaData.setSwA(true);
paramMetaData.setMimeTypes(mimePart.getMimeTypes());
break;
}
}
}
}
}
}
}
}
protected void processWebMethods(EndpointMetaData epMetaData, Class wsClass)
{
epMetaData.clearOperations();
// Process @WebMethod annotations
int webMethodCount = 0;
for (Method method : wsClass.getMethods())
{
WebMethod annotation = method.getAnnotation(WebMethod.class);
boolean exclude = annotation != null && annotation.exclude();
if (!exclude && (annotation != null || wsClass.isInterface()))
{
processWebMethod(epMetaData, method);
webMethodCount++;
}
}
// @WebService should expose all inherited methods if @WebMethod is never specified
if (webMethodCount == 0 && !wsClass.isInterface())
{
for (Method method : wsClass.getMethods())
{
WebMethod annotation = method.getAnnotation(WebMethod.class);
boolean exclude = annotation != null && annotation.exclude();
if (!exclude && method.getDeclaringClass() != Object.class)
{
processWebMethod(epMetaData, method);
webMethodCount++;
}
}
}
if (webMethodCount == 0)
throw new WSException("No exposable methods found");
}
protected void initWrapperGenerator(ClassLoader loader)
{
// Use the dynamic generator by default. Otherwise reset the last
if (wrapperGenerator == null)
wrapperGenerator = new DynamicWrapperGenerator(loader);
else wrapperGenerator.reset(loader);
}
protected void resetMetaDataBuilder(ClassLoader loader)
{
initWrapperGenerator(loader);
javaTypes.clear();
typeRefs.clear();
jaxbCtx = null;
}
protected void createJAXBContext(EndpointMetaData epMetaData)
{
try
{
String targetNS = epMetaData.getPortTypeName().getNamespaceURI().intern();
if (log.isDebugEnabled())
log.debug("JAXBContext [types=" + javaTypes + ",tns=" + targetNS + "]");
JAXBContextFactory factory = JAXBContextFactory.newInstance();
// JAXBIntros may mofiy the WSDL being generated
// only true for server side invocation, tooling (WSProvide) doesnt support this
BindingCustomization bindingCustomization = null;
if(epMetaData instanceof ServerEndpointMetaData)
{
Endpoint endpoint = ((ServerEndpointMetaData)epMetaData).getEndpoint();
bindingCustomization = endpoint!=null ? endpoint.getAttachment(BindingCustomization.class) : null;
}
jaxbCtx = factory.createContext(
javaTypes.toArray(new Class[0]),
typeRefs,
targetNS,
false, bindingCustomization
);
}
catch (WSException ex)
{
throw new IllegalStateException("Cannot build JAXB context", ex);
}
}
private void populateXmlType(FaultMetaData faultMetaData)
{
EndpointMetaData epMetaData = faultMetaData.getOperationMetaData().getEndpointMetaData();
TypesMetaData types = epMetaData.getServiceMetaData().getTypesMetaData();
QName xmlType = faultMetaData.getXmlType();
String faultBeanName = faultMetaData.getFaultBeanName();
types.addTypeMapping(new TypeMappingMetaData(types, xmlType, faultBeanName));
}
private void populateXmlType(ParameterMetaData paramMetaData)
{
EndpointMetaData epMetaData = paramMetaData.getOperationMetaData().getEndpointMetaData();
TypesMetaData types = epMetaData.getServiceMetaData().getTypesMetaData();
QName xmlName = paramMetaData.getXmlName();
QName xmlType = paramMetaData.getXmlType();
Class javaType = paramMetaData.getJavaType();
String javaName = paramMetaData.getJavaTypeName();
if (xmlType == null)
{
try
{
xmlType = jaxbCtx.getTypeName(new TypeReference(xmlName, javaType));
}
catch (IllegalArgumentException e)
{
throw new IllegalStateException("Cannot obtain xml type for: [xmlName=" + xmlName + ",javaName=" + javaName + "]");
}
/* Anonymous type.
*
* Currently the design of our stack is based on the
* notion of their always being a unique type. In order to lookup the
* appropriate (de)serializer you must have a type. So we use a fake
* name. This is an illegal NCName, so it shouldn't collide.
*/
if (xmlType == null)
xmlType = new QName(xmlName.getNamespaceURI(), ">" + xmlName.getLocalPart());
paramMetaData.setXmlType(xmlType);
}
types.addTypeMapping(new TypeMappingMetaData(types, xmlType, javaName));
}
protected void populateXmlTypes(EndpointMetaData epMetaData)
{
for (OperationMetaData operation : epMetaData.getOperations())
{
// parameters
for (ParameterMetaData paramMetaData : operation.getParameters())
{
populateXmlType(paramMetaData);
}
// return value
ParameterMetaData returnParameter = operation.getReturnParameter();
if (returnParameter != null)
populateXmlType(returnParameter);
// faults
for (FaultMetaData faultMetaData : operation.getFaults())
{
populateXmlType(faultMetaData);
}
}
}
/**
* Set the wrapper generator for this builder.
*/
public void setWrapperGenerator(WrapperGenerator wrapperGenerator)
{
this.wrapperGenerator = wrapperGenerator;
}
}