/*
* 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.umdm;
// $Id: EndpointMetaData.java 9467 2009-03-02 11:31:40Z darran.lofthouse@jboss.com $
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.Set;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.xml.bind.JAXBContext;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import javax.xml.ws.Service.Mode;
import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.CommonBindingProvider;
import org.jboss.ws.core.CommonSOAPBinding;
import org.jboss.ws.core.binding.TypeMappingImpl;
import org.jboss.ws.core.jaxrpc.binding.JBossXBDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.JBossXBSerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.SOAPArrayDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.SOAPArraySerializerFactory;
import org.jboss.ws.core.jaxws.JAXBContextCache;
import org.jboss.ws.core.jaxws.JAXBContextFactory;
import org.jboss.ws.core.jaxws.JAXBDeserializerFactory;
import org.jboss.ws.core.jaxws.JAXBSerializerFactory;
import org.jboss.ws.core.jaxws.client.DispatchBinding;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.core.soap.Use;
import org.jboss.ws.metadata.accessor.AccessorFactory;
import org.jboss.ws.metadata.accessor.AccessorFactoryCreator;
import org.jboss.ws.metadata.accessor.JAXBAccessorFactoryCreator;
import org.jboss.ws.metadata.config.CommonConfig;
import org.jboss.ws.metadata.config.Configurable;
import org.jboss.ws.metadata.config.ConfigurationProvider;
import org.jboss.ws.metadata.config.EndpointFeature;
import org.jboss.ws.metadata.config.JBossWSConfigFactory;
import org.jboss.wsf.common.JavaUtils;
import org.jboss.wsf.spi.binding.BindingCustomization;
import org.jboss.wsf.spi.deployment.UnifiedVirtualFile;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedPortComponentRefMetaData;
import org.jboss.wsf.spi.metadata.j2ee.serviceref.UnifiedHandlerMetaData.HandlerType;
import com.sun.xml.bind.api.JAXBRIContext;
/**
* A Service component describes a set of endpoints.
*
* @author Thomas.Diesler@jboss.org
* @since 12-May-2005
*/
public abstract class EndpointMetaData extends ExtensibleMetaData implements ConfigurationProvider, InitalizableMetaData
{
// provide logging
private static Logger log = Logger.getLogger(EndpointMetaData.class);
public enum Type
{
JAXRPC, JAXWS
}
public static final Set<String> SUPPORTED_BINDINGS = new HashSet<String>();
static
{
SUPPORTED_BINDINGS.add(Constants.SOAP11HTTP_BINDING);
SUPPORTED_BINDINGS.add(Constants.SOAP12HTTP_BINDING);
SUPPORTED_BINDINGS.add(Constants.SOAP11HTTP_MTOM_BINDING);
SUPPORTED_BINDINGS.add(Constants.SOAP12HTTP_MTOM_BINDING);
SUPPORTED_BINDINGS.add(Constants.HTTP_BINDING);
}
// The parent meta data.
private ServiceMetaData serviceMetaData;
// The REQUIRED configuration meta data.
private EndpointConfigMetaData configMetaData;
// The REQUIRED name
private QName portName;
// The REQUIRED binding id
private String bindingId;
// The REQUIRED name of the WSDL interface/portType
private QName portTypeName;
// The endpoint interface name
private String seiName;
// The endpoint interface
private Class seiClass;
// The optional authentication method
private String authMethod;
// Arbitrary properties given by <call-property>
private Properties properties;
// The SOAPBinding style
private Style style;
// The SOAPBinding use
private Use use;
// The SOAPBinding parameter style
private ParameterStyle parameterStyle;
// The JAXWS ServiceMode
private Mode serviceMode;
// Whether the endpoint was deployed from annotations
private Type type;
// The list of service meta data
private List<OperationMetaData> operations = new ArrayList<OperationMetaData>();
// Maps the java method to the operation meta data
private Map<Method, OperationMetaData> opMetaDataCache = new HashMap<Method, OperationMetaData>();
// All of the registered types
private List<Class> registeredTypes = new ArrayList<Class>();
private ConfigObservable configObservable = new ConfigObservable();
private List<UnifiedPortComponentRefMetaData> serviceRefContrib = new ArrayList<UnifiedPortComponentRefMetaData>();
private JAXBContextCache jaxbCache = new JAXBContextCache();
private List<BindingCustomization> bindingCustomization = new ArrayList<BindingCustomization>();
public EndpointMetaData(ServiceMetaData service, QName portName, QName portTypeName, Type type)
{
this.serviceMetaData = service;
this.portName = portName;
this.portTypeName = portTypeName;
this.type = type;
// The default binding
this.bindingId = Constants.SOAP11HTTP_BINDING;
}
public ServiceMetaData getServiceMetaData()
{
return serviceMetaData;
}
public QName getPortName()
{
return portName;
}
public void setPortName(QName portName)
{
this.portName = portName;
}
public QName getPortTypeName()
{
return portTypeName;
}
public abstract String getEndpointAddress();
public abstract void setEndpointAddress(String endpointAddress);
public String getBindingId()
{
return bindingId;
}
public void setBindingId(String bindingId)
{
if (SUPPORTED_BINDINGS.contains(bindingId) == false)
throw new WSException("Unsupported binding: " + bindingId);
this.bindingId = bindingId;
}
public String getServiceEndpointInterfaceName()
{
return seiName;
}
public void setServiceEndpointInterfaceName(String seiName)
{
this.seiName = seiName;
this.seiClass = null;
UnifiedMetaData wsMetaData = serviceMetaData.getUnifiedMetaData();
if (wsMetaData.isEagerInitialized())
{
if (UnifiedMetaData.isFinalRelease() == false)
log.warn("Set SEI name after eager initialization", new IllegalStateException());
// reinitialize
initializeInternal();
}
}
/** Get the class loader associated with the endpoint meta data */
public ClassLoader getClassLoader()
{
return getServiceMetaData().getUnifiedMetaData().getClassLoader();
}
/**
* Load the service endpoint interface.
* It should only be cached during eager initialization.
*/
public Class getServiceEndpointInterface()
{
Class tmpClass = seiClass;
if (tmpClass == null && seiName != null)
{
try
{
ClassLoader classLoader = getClassLoader();
tmpClass = classLoader.loadClass(seiName);
if (serviceMetaData.getUnifiedMetaData().isEagerInitialized())
{
log.warn("Loading SEI after eager initialization");
seiClass = tmpClass;
}
}
catch (ClassNotFoundException ex)
{
throw new WSException("Cannot load service endpoint interface: " + seiName, ex);
}
}
return tmpClass;
}
public Use getEncodingStyle()
{
if (use == null)
{
use = Use.getDefaultUse();
log.debug("Using default encoding style: " + use);
}
return use;
}
public void setEncodingStyle(Use value)
{
if (value != null && use != null && !use.equals(value))
throw new WSException("Mixed encoding styles not supported");
log.trace("setEncodingStyle: " + value);
this.use = value;
}
public Style getStyle()
{
if (style == null)
{
style = Style.getDefaultStyle();
log.debug("Using default style: " + style);
}
return style;
}
public void setStyle(Style value)
{
if (value != null && style != null && !style.equals(value))
throw new WSException("Mixed styles not supported");
log.trace("setStyle: " + value);
this.style = value;
}
public ParameterStyle getParameterStyle()
{
if (parameterStyle == null)
{
parameterStyle = ParameterStyle.WRAPPED;
log.debug("Using default parameter style: " + parameterStyle);
}
return parameterStyle;
}
public void setParameterStyle(ParameterStyle value)
{
if (value != null && parameterStyle != null && !parameterStyle.equals(value))
throw new WSException("Mixed SOAP parameter styles not supported");
log.debug("setParameterStyle: " + value);
this.parameterStyle = value;
}
public Mode getServiceMode()
{
return serviceMode;
}
public void setServiceMode(Mode serviceMode)
{
this.serviceMode = serviceMode;
}
public Type getType()
{
return type;
}
public Collection<BindingCustomization> getBindingCustomizations()
{
return bindingCustomization;
}
public String getAuthMethod()
{
return authMethod;
}
public void setAuthMethod(String authMethod)
{
this.authMethod = authMethod;
}
public Properties getProperties()
{
if (null == this.properties)
this.properties = new Properties();
return this.properties;
}
public void setProperties(Properties properties)
{
this.properties = properties;
}
public List<OperationMetaData> getOperations()
{
return new ArrayList<OperationMetaData>(operations);
}
public OperationMetaData getOperation(QName xmlName)
{
OperationMetaData opMetaData = null;
for (OperationMetaData aux : operations)
{
QName opQName = aux.getQName();
String javaName = aux.getJavaName();
if (opQName.equals(xmlName) && !javaName.endsWith(Constants.ASYNC_METHOD_SUFFIX))
{
if (opMetaData == null)
{
opMetaData = aux;
}
else
{
throw new WSException("Cannot uniquely indentify operation: " + xmlName);
}
}
}
if (opMetaData == null && getStyle() == Style.DOCUMENT)
{
for (OperationMetaData auxOperation : operations)
{
ParameterMetaData paramMetaData = null;
for (ParameterMetaData auxParam : auxOperation.getParameters())
{
ParameterMode mode = auxParam.getMode();
if (!auxParam.isInHeader() && mode != ParameterMode.OUT)
{
paramMetaData = auxParam;
break;
}
}
if (paramMetaData != null && paramMetaData.getXmlName().equals(xmlName))
{
if (opMetaData == null)
{
opMetaData = auxOperation;
}
else
{
throw new WSException("Cannot uniquely indentify operation: " + xmlName);
}
}
}
}
return opMetaData;
}
public OperationMetaData getOperation(Method method)
{
if (opMetaDataCache.size() == 0)
{
// This can happen when the SEI mapping was not found
log.warn("Access to empty operation meta data cache, reinitializing");
initializeInternal();
}
OperationMetaData opMetaData = opMetaDataCache.get(method);
if (opMetaData == null)
{
for (OperationMetaData aux : operations)
{
boolean doesMatch = aux.getJavaMethod().equals(method);
// fallback for async methods
if (!doesMatch && method.getName().endsWith(Constants.ASYNC_METHOD_SUFFIX))
{
String name = method.getName();
name = name.substring(0, name.length() - 5);
doesMatch = aux.getJavaName().equals(name);
}
if (doesMatch)
{
opMetaDataCache.put(method, aux);
opMetaData = aux;
break;
}
}
}
return opMetaData;
}
public void addOperation(OperationMetaData opMetaData)
{
operations.add(opMetaData);
}
public void clearOperations()
{
operations.clear();
}
public void addHandlers(List<HandlerMetaData> configHandlers)
{
getEndpointConfigMetaData().addHandlers(configHandlers);
}
public void addHandler(HandlerMetaData handler)
{
getEndpointConfigMetaData().addHandler(handler);
}
public void clearHandlers()
{
getEndpointConfigMetaData().clearHandlers();
}
public List<HandlerMetaData> getHandlerMetaData(HandlerType type)
{
return getEndpointConfigMetaData().getHandlerMetaData(type);
}
public boolean isHandlersInitialized()
{
return getEndpointConfigMetaData().isHandlersInitialized();
}
public void setHandlersInitialized(boolean flag)
{
getEndpointConfigMetaData().setHandlersInitialized(flag);
}
public void validate()
{
getEndpointConfigMetaData().validate();
for (OperationMetaData opMetaData : operations)
opMetaData.validate();
}
/**
* @see UnifiedMetaData#eagerInitialize()
*/
public void eagerInitialize()
{
initializeInternal();
}
private void initializeInternal()
{
// reset sei class
seiClass = null;
getEndpointConfigMetaData().initializeInternal();
eagerInitializeOperations();
eagerInitializeTypes();
eagerInitializeAccessors();
eagerInitializeJAXBContextCache();
}
private void eagerInitializeOperations()
{
seiClass = getServiceEndpointInterface();
if (seiClass != null)
{
List<Method> unsynchronizedMethods = new ArrayList<Method>();
unsynchronizedMethods.addAll(Arrays.asList(seiClass.getMethods()));
for (OperationMetaData opMetaData : operations)
{
opMetaData.eagerInitialize(unsynchronizedMethods);
Method method = opMetaData.getJavaMethod();
if (method != null)
{
opMetaDataCache.put(method, opMetaData);
unsynchronizedMethods.remove(method);
}
}
}
}
private void eagerInitializeTypes()
{
TypeMappingImpl typeMapping = serviceMetaData.getTypeMapping();
List<TypeMappingMetaData> typeMappings = serviceMetaData.getTypesMetaData().getTypeMappings();
registeredTypes = new ArrayList<Class>(typeMappings.size());
for (TypeMappingMetaData tmMetaData : typeMappings)
{
String javaTypeName = tmMetaData.getJavaTypeName();
QName xmlType = tmMetaData.getXmlType();
if (xmlType != null)
{
List<Class> types = typeMapping.getJavaTypes(xmlType);
// TODO: Clarification. In which cases is the type already registered?
boolean registered = false;
for (Class current : types)
{
if (current.getName().equals(javaTypeName))
{
registeredTypes.add(current);
registered = true;
break;
}
}
if (registered == false)
{
try
{
ClassLoader classLoader = getClassLoader();
Class javaType = JavaUtils.loadJavaType(javaTypeName, classLoader);
if (JavaUtils.isPrimitive(javaTypeName))
javaType = JavaUtils.getWrapperType(javaType);
// Needed for runtime JAXB context
registeredTypes.add(javaType);
if (getEncodingStyle() == Use.ENCODED && javaType.isArray())
{
typeMapping.register(javaType, xmlType, new SOAPArraySerializerFactory(), new SOAPArrayDeserializerFactory());
}
else
{
if (getType() == Type.JAXWS)
{
typeMapping.register(javaType, xmlType, new JAXBSerializerFactory(), new JAXBDeserializerFactory());
}
else
{
typeMapping.register(javaType, xmlType, new JBossXBSerializerFactory(), new JBossXBDeserializerFactory());
}
}
}
catch (ClassNotFoundException e)
{
log.warn("Cannot load class for type: " + xmlType + "," + javaTypeName);
}
}
}
}
}
private void eagerInitializeAccessors()
{
// Collect the list of all used types
boolean useJAXBAccessorFactory = false;
List<Class> types = new ArrayList<Class>();
for (OperationMetaData opMetaData : operations)
{
for (ParameterMetaData paramMetaData : opMetaData.getParameters())
{
AccessorFactoryCreator factoryCreator = paramMetaData.getAccessorFactoryCreator();
if (factoryCreator instanceof JAXBAccessorFactoryCreator)
useJAXBAccessorFactory = true;
types.add(paramMetaData.getJavaType());
}
ParameterMetaData retParam = opMetaData.getReturnParameter();
if (retParam != null)
{
AccessorFactoryCreator factoryCreator = retParam.getAccessorFactoryCreator();
if (factoryCreator instanceof JAXBAccessorFactoryCreator)
useJAXBAccessorFactory = true;
types.add(retParam.getJavaType());
}
}
// Create a JAXBContext for those types
JAXBRIContext jaxbCtx = null;
if (useJAXBAccessorFactory)
{
Class[] typeArr = new Class[types.size()];
jaxbCtx = (JAXBRIContext)JAXBContextFactory.newInstance().createContext(types.toArray(typeArr));
}
// Create the accessors using a shared JAXBContext
for (OperationMetaData opMetaData : operations)
{
for (ParameterMetaData paramMetaData : opMetaData.getParameters())
{
createAccessor(paramMetaData, jaxbCtx);
}
ParameterMetaData retParam = opMetaData.getReturnParameter();
if (retParam != null)
createAccessor(retParam, jaxbCtx);
}
}
private void eagerInitializeJAXBContextCache()
{
// initialize jaxb context cache
if ("true".equalsIgnoreCase(System.getProperty(Constants.EAGER_INITIALIZE_JAXB_CONTEXT_CACHE)))
{
log.debug("Initializing JAXBContext cache...");
BindingCustomization bindingCustomization = null;
if (this instanceof ServerEndpointMetaData)
{
bindingCustomization = ((ServerEndpointMetaData)this).getEndpoint().getAttachment(BindingCustomization.class);
}
try
{
Class[] classes = getRegisteredTypes().toArray(new Class[0]);
JAXBContext context = JAXBContextFactory.newInstance().createContext(classes, bindingCustomization);
jaxbCache.add(classes, context);
}
catch (Exception e)
{
// ignore
}
}
}
private void createAccessor(ParameterMetaData paramMetaData, JAXBRIContext jaxbCtx)
{
AccessorFactoryCreator factoryCreator = paramMetaData.getAccessorFactoryCreator();
if (factoryCreator instanceof JAXBAccessorFactoryCreator)
((JAXBAccessorFactoryCreator)factoryCreator).setJAXBContext(jaxbCtx);
if (paramMetaData.getWrappedParameters() != null)
{
AccessorFactory factory = factoryCreator.create(paramMetaData);
for (WrappedParameter wParam : paramMetaData.getWrappedParameters())
wParam.setAccessor(factory.create(wParam));
}
}
// ---------------------------------------------------------------
// Configuration provider impl
/**
* Callback for components that require configuration through jbossws-dd
*/
public void configure(Configurable configurable)
{
CommonConfig config = getConfig();
// SOAPBinding configuration
if (configurable instanceof CommonBindingProvider)
{
log.debug("Configure SOAPBinding");
if (config.hasFeature(EndpointFeature.MTOM))
{
CommonBindingProvider provider = (CommonBindingProvider)configurable;
((CommonSOAPBinding)provider.getCommonBinding()).setMTOMEnabled(true);
log.debug("Enable MTOM on endpoint " + getPortName());
}
}
else if (configurable instanceof DispatchBinding)
{
DispatchBinding dpb = (DispatchBinding)configurable;
dpb.setValidateDispatch(config.hasFeature(EndpointFeature.VALIDATE_DISPATCH));
}
}
public UnifiedVirtualFile getRootFile()
{
return getServiceMetaData().getUnifiedMetaData().getRootFile();
}
public void registerConfigObserver(Configurable observer)
{
configObservable.addObserver(observer);
}
public JAXBContextCache getJaxbCache()
{
return jaxbCache;
}
public String getConfigFile()
{
return getEndpointConfigMetaData().getConfigFile();
}
public String getConfigName()
{
return getEndpointConfigMetaData().getConfigName();
}
public EndpointConfigMetaData getEndpointConfigMetaData()
{
if (configMetaData == null)
configMetaData = new EndpointConfigMetaData(this);
return this.configMetaData;
}
public CommonConfig getConfig()
{
EndpointConfigMetaData ecmd = getEndpointConfigMetaData();
CommonConfig config = ecmd.getConfig();
// Make sure we have a configuration
if (config == null)
{
// No base configuration.
initEndpointConfigMetaData(ecmd, null);
config = ecmd.getConfig();
}
return config;
}
public void setConfigName(String configName)
{
setConfigNameInternal(configName, null);
}
public void setConfigName(String configName, String configFile)
{
setConfigNameInternal(configName, configFile);
}
private void setConfigNameInternal(String configName, String configFile)
{
if (configName == null)
throw new IllegalArgumentException("Config name cannot be null");
if (configFile == null)
{
configFile = getEndpointConfigMetaData().getConfigFile();
}
if (configName.equals(getEndpointConfigMetaData().getConfigName()) == false || configFile.equals(getEndpointConfigMetaData().getConfigFile()) == false)
{
log.debug("Reconfiguration forced, new config is '" + configName + "' file is '" + configFile + "'");
this.configMetaData = createEndpointConfigMetaData(configName, configFile);
configObservable.doNotify(configName);
}
}
/**
* The factory method to create and initialise a new EndpointConfigMetaData, the current
* EndpointConfigMetaData will be used as the base to backup the RMMD.
*
* This method does not set the EndpointConfigMetaData as it can be used by clients to create
* a local configuration not stored in the EndpointMetaData.
*/
protected EndpointConfigMetaData createEndpointConfigMetaData(String configName, String configFile)
{
EndpointConfigMetaData ecmd = new EndpointConfigMetaData(this);
ecmd.setConfigName(configName);
ecmd.setConfigFile(configFile);
initEndpointConfigMetaData(ecmd, configMetaData);
return ecmd;
}
public void initEndpointConfig()
{
EndpointConfigMetaData ecmd = getEndpointConfigMetaData();
// At the time this method is called initialisation may have already happened
// always take the current ECMD as a base in case there is anything to backup.
initEndpointConfigMetaData(ecmd, ecmd);
}
/**
* Initialise the toInitialise EndpointConfigMeta but first backup the RM Meta Data from
* the base EndpointConfigMetaData.
*
* @param toInitialise - The EndpointConfigMetaData to initialise.
* @param base - The base EndpointConfigMetaData to take the RMMD from.
*/
private void initEndpointConfigMetaData(EndpointConfigMetaData toInitialise, EndpointConfigMetaData base)
{
String configName = toInitialise.getConfigName();
String configFile = toInitialise.getConfigFile();
log.debug("Create new config [name=" + configName + ",file=" + configFile + "]");
JBossWSConfigFactory factory = JBossWSConfigFactory.newInstance();
CommonConfig config = factory.getConfig(getRootFile(), configName, configFile);
toInitialise.setConfig(config);
toInitialise.configHandlerMetaData();
}
public List<Class> getRegisteredTypes()
{
return Collections.unmodifiableList(registeredTypes);
}
class ConfigObservable extends Observable
{
private ReferenceQueue<WeakReference<Observer>> queue = new ReferenceQueue<WeakReference<Observer>>();
private List<WeakReference<Observer>> observer = new ArrayList<WeakReference<Observer>>();
public void doNotify(Object object)
{
setChanged();
notifyObservers(object);
}
public synchronized void addObserver(Observer o)
{
clearCollected();
observer.add(new WeakReference(o, queue));
}
public synchronized void deleteObserver(Observer o)
{
clearCollected();
for (WeakReference<Observer> w : observer)
{
Observer tmp = w.get();
if (tmp != null && tmp.equals(o))
{
observer.remove(o);
break;
}
}
}
public void notifyObservers()
{
notifyObservers(null);
}
public void notifyObservers(Object arg)
{
clearCollected();
if (hasChanged())
{
for (WeakReference<Observer> w : observer)
{
Observer tmp = w.get();
if (tmp != null)
{
tmp.update(this, arg);
}
}
}
}
private void clearCollected()
{
Reference ref;
while ((ref = queue.poll()) != null)
{
observer.remove(ref);
}
}
}
public List<UnifiedPortComponentRefMetaData> getServiceRefContrib()
{
return serviceRefContrib;
}
public boolean matches(UnifiedPortComponentRefMetaData pcRef)
{
String seiName = pcRef.getServiceEndpointInterface();
QName portName = pcRef.getPortQName();
boolean match;
if (seiName != null && portName != null)
{
match = getServiceEndpointInterfaceName().equals(seiName) && getPortName().equals(portName);
}
else
{
match = getServiceEndpointInterfaceName().equals(seiName) || getPortName().equals(portName);
}
return match;
}
}