blob: 3d58bb09486ece80da06a832dd7f00b0f063e54a [file] [log] [blame]
/*
* 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.openejb.server.cxf.transport.util;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.binding.BindingFactory;
import org.apache.cxf.binding.BindingFactoryManager;
import org.apache.cxf.bus.CXFBusFactory;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.ClassUnwrapper;
import org.apache.cxf.databinding.DataBinding;
import org.apache.cxf.endpoint.AbstractEndpointFactory;
import org.apache.cxf.feature.AbstractFeature;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.InterceptorProvider;
import org.apache.cxf.management.InstrumentationManager;
import org.apache.cxf.management.counters.CounterRepository;
import org.apache.cxf.management.jmx.InstrumentationManagerImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.http.CXFAuthenticator;
import org.apache.cxf.transport.http.HttpDestinationFactory;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.assembler.classic.OpenEjbConfiguration;
import org.apache.openejb.assembler.classic.ServiceInfo;
import org.apache.openejb.assembler.classic.event.AssemblerBeforeApplicationDestroyed;
import org.apache.openejb.assembler.classic.event.AssemblerDestroyed;
import org.apache.openejb.assembler.classic.util.ServiceConfiguration;
import org.apache.openejb.assembler.classic.util.ServiceInfos;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.monitoring.LocalMBeanServer;
import org.apache.openejb.observer.Observes;
import org.apache.openejb.server.cxf.transport.OpenEJBHttpDestinationFactory;
import org.apache.openejb.server.cxf.transport.event.BusCreated;
import org.apache.openejb.util.PropertiesHelper;
import org.apache.openejb.util.reflection.Reflections;
import org.apache.webbeans.intercept.DefaultInterceptorHandler;
import org.apache.webbeans.proxy.InterceptorHandler;
import org.apache.webbeans.proxy.OwbInterceptorProxy;
import org.apache.webbeans.util.ExceptionUtil;
import javax.management.MBeanServer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import static org.apache.webbeans.proxy.InterceptorDecoratorProxyFactory.FIELD_INTERCEPTOR_HANDLER;
public final class CxfUtil {
public static final String ENDPOINT_PROPERTIES = "properties";
public static final String FEATURES = "features";
public static final String IN_INTERCEPTORS = "in-interceptors";
public static final String IN_FAULT_INTERCEPTORS = "in-fault-interceptors";
public static final String OUT_INTERCEPTORS = "out-interceptors";
public static final String OUT_FAULT_INTERCEPTORS = "out-fault-interceptors";
public static final String DATABINDING = "databinding";
public static final String ADDRESS = "address";
public static final String PUBLISHED_URL = "published-url";
public static final String DEBUG = "debug";
public static final String BUS_PREFIX = "org.apache.openejb.cxf.bus.";
public static final String BUS_CONFIGURED_FLAG = "openejb.cxf.bus.configured";
private static final AtomicReference<Bus> DEFAULT_BUS = new AtomicReference<>();
private static final AtomicInteger USER_COUNT = new AtomicInteger();
private static Map<String, BindingFactory> bindingFactoryMap;
private CxfUtil() {
// no-op
}
public static void release() { // symmetric of configureBus(), when last caller of configureBus() is calls this bus is destroyed
if (USER_COUNT.decrementAndGet() == 0) {
final Bus b = DEFAULT_BUS.get();
if (b != null) {
b.shutdown(true);
}
}
}
public static boolean hasService(final String name) {
return bindingFactoryMap != null && bindingFactoryMap.containsKey(name);
}
private static Bus initDefaultBus() {
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(CxfUtil.class.getClassLoader());
try { // create the bus reusing cxf logic but skipping factory lookup
final Bus bus = BusFactory.newInstance(CXFBusFactory.class.getName()).createBus();
bus.setId(SystemInstance.get().getProperty("openejb.cxf.bus.id", "openejb.cxf.bus"));
final BindingFactoryManager bfm = bus.getExtension(BindingFactoryManager.class);
bindingFactoryMap = (Map<String, BindingFactory>) Reflections.get(bfm, "bindingFactories");
bus.setExtension(new OpenEJBHttpDestinationFactory(), HttpDestinationFactory.class);
// ensure client proxies can use app classes
CXFBusFactory.setDefaultBus(Bus.class.cast(Proxy.newProxyInstance(CxfUtil.class.getClassLoader(), new Class<?>[]{Bus.class}, new ClientAwareBusHandler())));
bus.setProperty(ClassUnwrapper.class.getName(), new ClassUnwrapper() {
@Override
public Class<?> getRealClass(final Object o) {
final Class<?> aClass = o.getClass();
Class<?> c = aClass;
while (c.getName().contains("$$")) {
c = c.getSuperclass();
}
return c == Object.class ? aClass : c;
}
@Override
public Class<?> getRealClassFromClass(Class<?> aClass) {
return aClass;
}
@Override
public Object getRealObject(Object o) {
// special case for OWB proxies - ie, a webservice endpoint with a CDI interceptor
// we'll want to unwrap this and set the field on the proxied instance, rather than set the field
// straight on the proxy
if (o instanceof OwbInterceptorProxy) {
return getProxiedInstance(o);
}
return o;
}
private Object getProxiedInstance(Object o) {
try {
final Field field = o.getClass().getDeclaredField(FIELD_INTERCEPTOR_HANDLER);
field.setAccessible(true);
final Object fieldValue = field.get(o);
if (fieldValue instanceof DefaultInterceptorHandler) {
final DefaultInterceptorHandler handler = (DefaultInterceptorHandler) fieldValue;
return handler.getTarget();
} else {
throw new IllegalStateException("Expected OwbInterceptorProxy handler to be an instance of Default Interceptor Handler.");
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
});
SystemInstance.get().addObserver(new LifecycleManager());
initAuthenticators();
return bus; // we keep as internal the real bus and just expose to cxf the client aware bus to be able to cast it easily
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
private static void initAuthenticators() { // TODO: drop when we get a fully supporting java 9 version of CXF
/* Removing to bump to CXF 3.3.0 which supports Java 11
try {
CXFAuthenticator.addAuthenticator();
} catch (final RuntimeException re) {
// we swallow it while cxf doesnt support java 9, this workaround is enough to make most of cases passing
}*/
}
public static Bus getBus() {
Bus bus = DEFAULT_BUS.get();
if (bus == null) {
synchronized (DEFAULT_BUS) { // synch could be better "in case off
// " but with our lifecycle it is far enough since it is thread safe
bus = DEFAULT_BUS.get();
if (bus == null) {
bus = initDefaultBus();
DEFAULT_BUS.set(bus);
}
}
}
return bus;
}
public static ClassLoader initBusLoader() {
final ClassLoader loader = CxfUtil.getBus().getExtension(ClassLoader.class);
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (loader != null) {
if (CxfContainerClassLoader.class.isInstance(loader) && !CxfContainerClassLoader.class.isInstance(tccl)) {
CxfContainerClassLoader.class.cast(loader).tccl(tccl);
}
return loader;
}
return tccl;
}
public static void clearBusLoader(final ClassLoader old) {
final ClassLoader loader = CxfUtil.getBus().getExtension(ClassLoader.class);
if (loader != null && CxfContainerClassLoader.class.isInstance(loader)
&& (old == null || !CxfContainerClassLoader.class.isInstance(old))) {
CxfContainerClassLoader.class.cast(loader).clear();
}
Thread.currentThread().setContextClassLoader(old);
}
public static void configureEndpoint(final AbstractEndpointFactory svrFactory, final ServiceConfiguration configuration, final String prefix) {
final Properties beanConfig = configuration.getProperties();
if (beanConfig == null || beanConfig.isEmpty()) {
return;
}
final Collection<ServiceInfo> availableServices = configuration.getAvailableServices();
// endpoint properties
final Properties properties = ServiceInfos.serviceProperties(availableServices, beanConfig.getProperty(prefix + ENDPOINT_PROPERTIES));
if (properties != null) {
svrFactory.setProperties(PropertiesHelper.map(properties));
}
final String debugKey = prefix + DEBUG;
if ("true".equalsIgnoreCase(beanConfig.getProperty(debugKey, SystemInstance.get().getOptions().get(debugKey, "false")))) {
svrFactory.getProperties(true).put("faultStackTraceEnabled", "true");
}
// endpoint features
final String featuresIds = beanConfig.getProperty(prefix + FEATURES);
if (featuresIds != null) {
final List<? extends Feature> features = createFeatures(availableServices, featuresIds);
svrFactory.setFeatures(features);
}
configureInterceptors(svrFactory, prefix, availableServices, beanConfig);
// databinding
final String databinding = beanConfig.getProperty(prefix + DATABINDING);
if (databinding != null && !databinding.trim().isEmpty()) {
Object instance = ServiceInfos.resolve(availableServices, databinding);
if (instance == null) { // maybe id == classname
try {
instance = Thread.currentThread().getContextClassLoader().loadClass(databinding).newInstance();
} catch (Exception e) {
// ignore
}
}
if (!DataBinding.class.isInstance(instance)) {
throw new OpenEJBRuntimeException(instance + " is not a " + DataBinding.class.getName()
+ ", please check configuration of service [id=" + databinding + "]");
}
svrFactory.setDataBinding((DataBinding) instance);
}
// address: easier than using openejb-jar.xml
final String changedAddress = beanConfig.getProperty(prefix + ADDRESS);
if (changedAddress != null && !changedAddress.trim().isEmpty()) {
svrFactory.setAddress(changedAddress);
}
// published url
final String publishedUrl = beanConfig.getProperty(prefix + PUBLISHED_URL);
if (publishedUrl != null && !publishedUrl.trim().isEmpty()) {
svrFactory.setPublishedEndpointUrl(publishedUrl);
}
}
public static void configureInterceptors(final InterceptorProvider abip, final String prefix, final Collection<ServiceInfo> availableServices, final Properties beanConfig) {
// interceptors
final String inInterceptorsIds = beanConfig.getProperty(prefix + IN_INTERCEPTORS);
if (inInterceptorsIds != null && !inInterceptorsIds.trim().isEmpty()) {
abip.getInInterceptors().addAll(createInterceptors(availableServices, inInterceptorsIds));
}
final String inFaultInterceptorsIds = beanConfig.getProperty(prefix + IN_FAULT_INTERCEPTORS);
if (inFaultInterceptorsIds != null && !inFaultInterceptorsIds.trim().isEmpty()) {
abip.getInFaultInterceptors().addAll(createInterceptors(availableServices, inFaultInterceptorsIds));
}
final String outInterceptorsIds = beanConfig.getProperty(prefix + OUT_INTERCEPTORS);
if (outInterceptorsIds != null && !outInterceptorsIds.trim().isEmpty()) {
abip.getOutInterceptors().addAll(createInterceptors(availableServices, outInterceptorsIds));
}
final String outFaultInterceptorsIds = beanConfig.getProperty(prefix + OUT_FAULT_INTERCEPTORS);
if (outFaultInterceptorsIds != null && !outFaultInterceptorsIds.trim().isEmpty()) {
abip.getOutFaultInterceptors().addAll(createInterceptors(availableServices, outFaultInterceptorsIds));
}
}
public static List<Feature> createFeatures(final Collection<ServiceInfo> availableServices, final String featuresIds) {
final List<?> features = ServiceInfos.resolve(availableServices, featuresIds.split(","));
for (final Object instance : features) {
if (!AbstractFeature.class.isInstance(instance)) {
throw new OpenEJBRuntimeException("feature should inherit from " + AbstractFeature.class.getName());
}
}
return (List<Feature>) features;
}
public static List<Interceptor<? extends Message>> createInterceptors(final Collection<ServiceInfo> availableServices, final String ids) {
final List<?> instances = ServiceInfos.resolve(availableServices, ids.split(","));
for (Object instance : instances) {
if (!Interceptor.class.isInstance(instance)) {
throw new OpenEJBRuntimeException("interceptors should implement " + Interceptor.class.getName());
}
}
return (List<Interceptor<? extends Message>>) instances;
}
public static void configureBus() {
if (USER_COUNT.incrementAndGet() > 1) {
return;
}
final SystemInstance systemInstance = SystemInstance.get();
final Bus bus = getBus();
// ensure cxf classes are loaded from container to avoid conflicts with app
if ("true".equalsIgnoreCase(systemInstance.getProperty("openejb.cxf.CxfContainerClassLoader", "true"))) {
bus.setExtension(new CxfContainerClassLoader(), ClassLoader.class);
}
// activate jmx, by default isEnabled() == false in InstrumentationManagerImpl
final boolean hasMonitoring = hasMonitoring(systemInstance);
if (hasMonitoring || "true".equalsIgnoreCase(systemInstance.getProperty("openejb.cxf.jmx", "true"))) {
final InstrumentationManager mgr = bus.getExtension(InstrumentationManager.class);
if (InstrumentationManagerImpl.class.isInstance(mgr)) {
bus.setExtension(LocalMBeanServer.get(), MBeanServer.class); // just to keep everything consistent
final InstrumentationManagerImpl manager = InstrumentationManagerImpl.class.cast(mgr);
manager.setEnabled(true);
manager.setServer(LocalMBeanServer.get());
try { // avoid to bother our nice logs
LogUtils.getL7dLogger(InstrumentationManagerImpl.class).setLevel(Level.WARNING);
} catch (final Throwable th) {
// no-op
}
// failed when bus was constructed or even if passed we switch the MBeanServer
manager.init();
}
}
if (hasMonitoring) {
new CounterRepository().setBus(bus);
}
final ServiceConfiguration configuration = new ServiceConfiguration(systemInstance.getProperties(),
systemInstance.getComponent(OpenEjbConfiguration.class).facilities.services);
final Collection<ServiceInfo> serviceInfos = configuration.getAvailableServices();
Properties properties = configuration.getProperties();
if (properties == null) {
properties = new Properties();
}
final String featuresIds = properties.getProperty(BUS_PREFIX + FEATURES);
if (featuresIds != null) {
final List<Feature> features = createFeatures(serviceInfos, featuresIds);
if (features != null) {
features.addAll(bus.getFeatures());
bus.setFeatures(features);
}
}
final Properties busProperties = ServiceInfos.serviceProperties(serviceInfos, properties.getProperty(BUS_PREFIX + ENDPOINT_PROPERTIES));
if (busProperties != null) {
bus.getProperties().putAll(PropertiesHelper.map(busProperties));
}
configureInterceptors(bus, BUS_PREFIX, serviceInfos, configuration.getProperties());
systemInstance.getProperties().setProperty(BUS_CONFIGURED_FLAG, "true");
systemInstance.fireEvent(new BusCreated(bus));
}
private static boolean hasMonitoring(final SystemInstance systemInstance) {
return "true".equalsIgnoreCase(systemInstance.getProperty("openejb.cxf.monitoring.jmx", "false"));
}
private static class ClientAwareBusHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final Bus bus = getBus();
// when creating a client it is important to use the application loader to be able to load application classes
// it is the default case but using our own CxfClassLoader we make it wrong so simply skip it when calling a client
// and no app classloader is registered
if ("getExtension".equals(method.getName()) && args != null && args.length == 1 && ClassLoader.class.equals(args[0])) {
final ClassLoader extensionLoader = ClassLoader.class.cast(method.invoke(bus, args));
if (CxfContainerClassLoader.class.isInstance(extensionLoader) && !CxfContainerClassLoader.class.cast(extensionLoader).hasTccl()) {
return null;
}
return extensionLoader;
}
return method.invoke(bus, args);
}
}
public static class LifecycleManager {
public void destroy(@Observes final AssemblerDestroyed ignored) {
final SystemInstance systemInstance = SystemInstance.get();
final Bus bus = getBus();
if ("true".equalsIgnoreCase(systemInstance.getProperty("openejb.cxf.jmx", "true"))) {
final InstrumentationManager mgr = bus.getExtension(InstrumentationManager.class);
if (InstrumentationManagerImpl.class.isInstance(mgr)) {
mgr.shutdown();
}
}
systemInstance.removeObserver(this);
}
public void destroy(@Observes final AssemblerBeforeApplicationDestroyed ignored) {
final SystemInstance systemInstance = SystemInstance.get();
final Bus bus = getBus();
// avoid to leak, we can enhance it to remove endpoints by app but not sure it does worth the effort
// alternative can be a bus per app but would enforce us to change some deeper part of our config/design
if ("true".equalsIgnoreCase(systemInstance.getProperty("openejb.cxf.monitoring.jmx.clear-on-undeploy", "true"))) {
final CounterRepository repo = bus.getExtension(CounterRepository.class);
if (repo != null) {
repo.getCounters().clear();
}
}
}
}
}