/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.wsf.stack.cxf.client;
import static org.jboss.ws.common.Messages.MESSAGES;
import static org.jboss.wsf.stack.cxf.client.Constants.JBWS_CXF_DISABLE_SCHEMA_CACHE;
import static org.jboss.wsf.stack.cxf.client.Constants.NEW_BUS_STRATEGY;
import static org.jboss.wsf.stack.cxf.client.Constants.TCCL_BUS_STRATEGY;
import static org.jboss.wsf.stack.cxf.client.Constants.THREAD_BUS_STRATEGY;
import static org.jboss.wsf.stack.cxf.client.SecurityActions.getContextClassLoader;
import static org.jboss.wsf.stack.cxf.client.SecurityActions.setContextClassLoader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import javax.xml.bind.JAXBContext;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Endpoint;
import javax.xml.ws.EndpointContext;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.spi.Invoker;
import javax.xml.ws.spi.ServiceDelegate;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.DispatchImpl;
import org.apache.cxf.jaxws.ServiceImpl;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.wsdl.WSDLManager;
import org.apache.cxf.wsdl11.WSDLManagerImpl;
import org.jboss.ws.api.configuration.AbstractClientFeature;
import org.jboss.ws.common.management.AbstractServerConfig;
import org.jboss.ws.common.utils.DelegateClassLoader;
import org.jboss.wsf.spi.classloading.ClassLoaderProvider;
import org.jboss.wsf.spi.management.ServerConfig;
import org.jboss.wsf.spi.metadata.config.ClientConfig;
import org.jboss.wsf.spi.metadata.config.ConfigMetaDataParser;
import org.jboss.wsf.spi.metadata.config.ConfigRoot;
import org.jboss.wsf.stack.cxf.Loggers;
import org.jboss.wsf.stack.cxf.Messages;
import org.jboss.wsf.stack.cxf.client.configuration.CXFClientConfigurer;
import org.jboss.wsf.stack.cxf.client.configuration.HandlerChainSortInterceptor;
import org.jboss.wsf.stack.cxf.client.configuration.JBossWSBusFactory;
import org.w3c.dom.Element;
import org.jboss.logging.Logger;
/**
* A custom javax.xml.ws.spi.Provider implementation
* extending the CXF one while adding few customizations.
*
* The most important customization is on the CXF Bus used
* the Endpoint.publish() or client.
* In particular, when a client is created, the thread
* default bus, thread context classloader bus and the
* bus used for the client being created depend on the
* selected strategy:
*
* * THREAD_BUS strategy
*
* Bus used for client
* =======================================
* | | Client Bus |
* =======================================
* | Default | NULL | New bus (Z) |
* | Thread |-------------------------|
* | Bus | Bus X | Bus X |
* =======================================
*
* State of buses before and after client creation
* =======================================
* | Bus | BEFORE | AFTER |
* =======================================
* | Default | NULL | New bus (Z) |
* | Thread |--------------------------|
* | Bus | Bus X | Bus X |
* =======================================
* | TCCL | NULL | NULL |
* | Bus |--------------------------|
* | | Bus Y | Bus Y |
* =======================================
*
*
* * NEW_BUS strategy
*
* Bus used for client
* =======================================
* | | Client Bus |
* =======================================
* | Default | NULL | New bus |
* | Thread |-------------------------|
* | Bus | Bus X | New bus |
* =======================================
*
* State of buses before and after client creation
* =======================================
* | Bus | BEFORE | AFTER |
* =======================================
* | Default | NULL | NULL |
* | Thread |--------------------------|
* | Bus | Bus X | Bus X |
* =======================================
* | TCCL | NULL | NULL |
* | Bus |--------------------------|
* | | Bus Y | Bus Y |
* =======================================
*
*
* * TCCL_BUS strategy
*
* Bus used for client
* =======================================
* | | Client Bus |
* =======================================
* | TCCL | NULL | New bus (Z) |
* | Bus |-------------------------|
* | | Bus Y | Bus Y |
* =======================================
*
* State of buses before and after client creation
* =======================================
* | Bus | BEFORE | AFTER |
* =======================================
* | Default | NULL | NULL |
* | Thread |--------------------------|
* | Bus | Bus X | Bus X |
* =======================================
* | TCCL | NULL | New bus (Z) |
* | Bus |--------------------------|
* | | Bus Y | Bus Y |
* =======================================
*
*
*
* This class also ensures a proper context classloader is set
* (required on JBoss AS 7, as the TCCL does not include
* implementation classes by default)
*
* @author alessio.soldano@jboss.com
* @since 27-Aug-2010
*
*/
public class ProviderImpl extends org.apache.cxf.jaxws22.spi.ProviderImpl
{
@Override
protected org.apache.cxf.jaxws.EndpointImpl createEndpointImpl(Bus bus, String bindingId, Object implementor,
WebServiceFeature... features)
{
Boolean db = (Boolean)bus.getProperty(Constants.DEPLOYMENT_BUS);
if (db != null && db)
{
Loggers.ROOT_LOGGER.cannotUseCurrentDepBusForStartingNewEndpoint();
bus = BusFactory.newInstance().createBus();
}
return super.createEndpointImpl(bus, bindingId, implementor, features);
}
@Override
public Endpoint createEndpoint(String bindingId, Object implementor) {
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
setValidThreadDefaultBus();
return new DelegateEndpointImpl(super.createEndpoint(bindingId, implementor));
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
public Endpoint createEndpoint(String bindingId,
Object implementor,
WebServiceFeature ... features) {
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
setValidThreadDefaultBus();
return new DelegateEndpointImpl(super.createEndpoint(bindingId, implementor, features));
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
public Endpoint createEndpoint(String bindingId, Class<?> implementorClass,
Invoker invoker, WebServiceFeature ... features) {
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
setValidThreadDefaultBus();
return new DelegateEndpointImpl(super.createEndpoint(bindingId, implementorClass, invoker, features));
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@SuppressWarnings("rawtypes")
@Override
public ServiceDelegate createServiceDelegate(URL url, QName qname, Class cls)
{
final String busStrategy = ClientBusSelector.getInstance().selectStrategy();
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
Bus orig = null;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
orig = BusFactory.getThreadDefaultBus(false);
Bus bus = getOrCreateBus(busStrategy, origClassLoader);
ServiceDelegate serviceDelegate = new JBossWSServiceImpl(bus, url, qname, cls);
setDisableCacheSchema(bus);
return serviceDelegate;
}
finally
{
restoreThreadDefaultBus(busStrategy, orig);
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@SuppressWarnings("rawtypes")
@Override
public ServiceDelegate createServiceDelegate(URL wsdlDocumentLocation, QName serviceName, Class serviceClass,
WebServiceFeature... features)
{
//check feature types
for (WebServiceFeature f : features) {
final String fName = f.getClass().getName();
if (!fName.startsWith("javax.xml.ws") && !fName.startsWith("org.jboss.ws")) {
throw Messages.MESSAGES.unknownFeature(f.getClass().getName());
}
}
final String busStrategy = ClientBusSelector.getInstance().selectStrategy(features);
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
Bus orig = null;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
orig = BusFactory.getThreadDefaultBus(false);
Bus bus = getOrCreateBus(busStrategy, origClassLoader);
ServiceDelegate serviceDelegate = new JBossWSServiceImpl(bus, wsdlDocumentLocation, serviceName, serviceClass, features);
setDisableCacheSchema(bus);
return serviceDelegate;
}
finally
{
restoreThreadDefaultBus(busStrategy, orig);
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
//JBWS-3973:Disable schema cache to workaround the intermittent failure
private void setDisableCacheSchema(Bus bus) {
if (bus.getExtension(WSDLManager.class) instanceof WSDLManagerImpl) {
WSDLManagerImpl wsdlManangerImpl = (WSDLManagerImpl)bus.getExtension(WSDLManager.class);
wsdlManangerImpl.setDisableSchemaCache(SecurityActions.getBoolean(JBWS_CXF_DISABLE_SCHEMA_CACHE, true));
}
}
private Bus getOrCreateBus(String strategy, ClassLoader threadContextClassLoader) {
Bus bus = null;
if (THREAD_BUS_STRATEGY.equals(strategy))
{
bus = setValidThreadDefaultBus();
}
else if (NEW_BUS_STRATEGY.equals(strategy))
{
bus = ClientBusSelector.getInstance().createNewBus();
//to prevent issues with CXF code using the default thread bus instead of the one returned here,
//set the new bus as thread one, given the line above could have not done this if the current
//thread is already assigned a bus
BusFactory.setThreadDefaultBus(bus);
}
else if (TCCL_BUS_STRATEGY.equals(strategy))
{
bus = JBossWSBusFactory.getClassLoaderDefaultBus(threadContextClassLoader, ClientBusSelector.getInstance());
//to prevent issues with CXF code using the default thread bus instead of the one returned here,
//set the bus as thread one, given the line above could have not done this if we already had a
//bus for the classloader and hence we did not create a new one
BusFactory.setThreadDefaultBus(bus);
}
return bus;
}
private void restoreThreadDefaultBus(final String busStrategy, final Bus origBus) {
if (origBus != null || !busStrategy.equals(Constants.THREAD_BUS_STRATEGY))
{
BusFactory.setThreadDefaultBus(origBus);
}
}
/**
* Ensure the current context classloader can load this ProviderImpl class.
*
* @return true if the TCCL has been changed, false otherwise
*/
static boolean checkAndFixContextClassLoader(ClassLoader origClassLoader)
{
try
{
origClassLoader.loadClass(ProviderImpl.class.getName());
}
catch (Exception e)
{
//[JBWS-3223] On AS7 the TCCL that's set for basic (non-ws-endpoint) servlet/ejb3
//apps doesn't have visibility on any WS implementation class, nor on any class
//coming from dependencies provided in the ws modules only. This means for instance
//the JAXBContext is not going to find a context impl, etc.
//In general, we need to change the TCCL using the classloader that has been used
//to load this javax.xml.ws.spi.Provider impl, which is the jaxws-client module.
ClassLoader clientClassLoader = ProviderImpl.class.getClassLoader();
//first ensure the default bus is loaded through the client classloader only
//(no deployment classloader contribution)
if (BusFactory.getDefaultBus(false) == null)
{
JBossWSBusFactory.getDefaultBus(clientClassLoader);
}
//then setup a new TCCL having visibility over both the client path (JBossWS
//jaxws-client module on AS7) and the the former TCCL (i.e. the deployment classloader)
setContextClassLoader(createDelegateClassLoader(clientClassLoader, origClassLoader));
return true;
}
return false;
}
private Bus setValidThreadDefaultBus()
{
//we need to prevent using the default bus when the current
//thread is not already associated to a bus. In those situations we create
//a new bus from scratch instead and link that to the thread.
Bus bus = BusFactory.getThreadDefaultBus(false);
if (bus == null)
{
bus = ClientBusSelector.getInstance().createNewBus(); //this also set thread local bus internally as it's not set yet
}
return bus;
}
private static DelegateClassLoader createDelegateClassLoader(final ClassLoader clientClassLoader, final ClassLoader origClassLoader)
{
SecurityManager sm = System.getSecurityManager();
if (sm == null)
{
return new DelegateClassLoader(clientClassLoader, origClassLoader);
}
else
{
return AccessController.doPrivileged(new PrivilegedAction<DelegateClassLoader>()
{
public DelegateClassLoader run()
{
return new DelegateClassLoader(clientClassLoader, origClassLoader);
}
});
}
}
/**
* A javax.xml.ws.Endpoint implementation delegating to a provided one
* that sets the TCCL before doing publish.
*
*/
static final class DelegateEndpointImpl extends Endpoint
{
private Endpoint delegate;
public DelegateEndpointImpl(Endpoint delegate)
{
this.delegate = delegate;
}
@Override
public Binding getBinding()
{
return delegate.getBinding();
}
@Override
public Object getImplementor()
{
return delegate.getImplementor();
}
@Override
public void publish(String address)
{
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
delegate.publish(address);
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
public void publish(Object serverContext)
{
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
delegate.publish(serverContext);
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
public void stop()
{
delegate.stop();
}
@Override
public boolean isPublished()
{
return delegate.isPublished();
}
@Override
public List<Source> getMetadata()
{
return delegate.getMetadata();
}
@Override
public void setMetadata(List<Source> metadata)
{
delegate.setMetadata(metadata);
}
@Override
public Executor getExecutor()
{
return delegate.getExecutor();
}
@Override
public void setExecutor(Executor executor)
{
delegate.setExecutor(executor);
}
@Override
public Map<String, Object> getProperties()
{
return delegate.getProperties();
}
@Override
public void setProperties(Map<String, Object> properties)
{
delegate.setProperties(properties);
}
@Override
public EndpointReference getEndpointReference(Element... referenceParameters)
{
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
return delegate.getEndpointReference(referenceParameters);
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
public <T extends EndpointReference> T getEndpointReference(Class<T> clazz, Element... referenceParameters)
{
ClassLoader origClassLoader = getContextClassLoader();
boolean restoreTCCL = false;
try
{
restoreTCCL = checkAndFixContextClassLoader(origClassLoader);
return delegate.getEndpointReference(clazz, referenceParameters);
}
finally
{
if (restoreTCCL)
setContextClassLoader(origClassLoader);
}
}
@Override
//jaxws2.2 api
public void setEndpointContext(EndpointContext ctxt)
{
delegate.setEndpointContext(ctxt);
}
@Override
//jaxws2.2 api
public void publish(javax.xml.ws.spi.http.HttpContext context)
{
delegate.publish(context);
}
}
/**
* An extension of the org.apache.cxf.jaxws.ServiceImpl allowing for
* setting JBossWS client default config handlers.
*
*/
static final class JBossWSServiceImpl extends ServiceImpl {
public JBossWSServiceImpl(Bus b, URL url, QName name, Class<?> cls, WebServiceFeature ... f) {
super(b, url, name, cls, f);
}
@Override
protected <T> T createPort(QName portName, EndpointReferenceType epr, Class<T> serviceEndpointInterface,
WebServiceFeature... features) {
T port = super.createPort(portName, epr, serviceEndpointInterface, features);
setupClient(port, serviceEndpointInterface, features);
return port;
}
@Override
public <T> Dispatch<T> createDispatch(QName portName,
Class<T> type,
JAXBContext context,
Mode mode,
WebServiceFeature... features) {
Dispatch<T> dispatch = super.createDispatch(portName, type, context, mode, features);
setupClient(dispatch, null, features);
return dispatch;
}
protected void setupClient(Object obj, Class<?> seiClass, WebServiceFeature... features) {
Binding binding = ((BindingProvider)obj).getBinding();
Client client = obj instanceof DispatchImpl<?> ? ((DispatchImpl<?>)obj).getClient() : ClientProxy.getClient(obj);
ClientConfig config = readConfig(seiClass);
if (config != null) {
CXFClientConfigurer helper = new CXFClientConfigurer();
helper.setupConfigHandlers(binding, config);
helper.setConfigProperties(client, config.getProperties());
}
client.getOutInterceptors().add(new HandlerChainSortInterceptor(binding)); //add this *after* the config has been set (if any)!
if (features != null) {
for (WebServiceFeature f : features) {
if (f instanceof AbstractClientFeature) {
((AbstractClientFeature)f).initialize(obj);
}
}
}
}
private static ServerConfig getServerConfig() {
if(System.getSecurityManager() == null) {
return AbstractServerConfig.getServerIntegrationServerConfig();
}
return AccessController.doPrivileged(AbstractServerConfig.GET_SERVER_INTEGRATION_SERVER_CONFIG);
}
private static ClientConfig readConfig(Class<?> seiClass) {
final String configName;
if (seiClass == null) { //nothing to do for Dispatch, as there's no SEI
configName = null;
} else {
configName = seiClass.getName();
InputStream is = null;
try
{
is = seiClass.getResourceAsStream("/" + ClientConfig.DEFAULT_CLIENT_CONFIG_FILE);
if (is != null) {
ConfigRoot config = ConfigMetaDataParser.parse(is);
ClientConfig cc = config != null ? config.getClientConfigByName(configName) : null;
if (cc != null) {
return cc;
}
}
}
catch (Exception e)
{
throw MESSAGES.couldNotReadConfiguration(ClientConfig.DEFAULT_CLIENT_CONFIG_FILE, e);
}
finally
{
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.getLogger(ProviderImpl.class).trace(e);
} //ignore
}
}
}
if (ClassLoaderProvider.isSet()) { //optimization for avoiding checking for a server config when we know for sure we're out-of-container
ServerConfig sc = getServerConfig();
if (sc != null) {
ClientConfig cf = configName != null ? sc.getClientConfig(configName) : null;
if (cf == null) {
cf = sc.getClientConfig(ClientConfig.STANDARD_CLIENT_CONFIG);
}
if (cf != null) {
return cf;
}
}
}
return null;
}
}
}