/* * 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.rs; import org.apache.cxf.Bus; import org.apache.cxf.binding.BindingFactoryManager; import org.apache.cxf.jaxrs.JAXRSBindingFactory; import org.apache.cxf.transport.DestinationFactory; import org.apache.cxf.transport.http.HTTPTransportFactory; import org.apache.openejb.cdi.WebBeansContextBeforeDeploy; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.observer.Observes; import org.apache.openejb.rest.AbstractRestThreadLocalProxy; import org.apache.openejb.rest.RESTResourceFinder; import org.apache.openejb.rest.ThreadLocalContextManager; import org.apache.openejb.server.ServiceException; import org.apache.openejb.server.cxf.transport.util.CxfUtil; import org.apache.openejb.server.rest.RESTService; import org.apache.openejb.server.rest.RsHttpListener; import org.apache.openejb.threads.task.CUTask; import org.apache.webbeans.annotation.AnyLiteral; import org.apache.webbeans.annotation.EmptyAnnotationLiteral; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.BeanManagerImpl; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.inject.spi.PassivationCapable; import javax.enterprise.util.AnnotationLiteral; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.container.ResourceContext; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Request; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Providers; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.net.Socket; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import static java.util.Arrays.asList; public class CxfRSService extends RESTService { private static final String NAME = "cxf-rs"; private DestinationFactory destinationFactory; private boolean factoryByListener; private Properties config; @Override public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException { throw new UnsupportedOperationException(getClass().getName() + " cannot be invoked directly"); } @Override public void service(final Socket socket) throws ServiceException, IOException { throw new UnsupportedOperationException(getClass().getName() + " cannot be invoked directly"); } @Override public String getName() { return NAME; } public void integrateCDIAndJaxRsInjections(@Observes final WebBeansContextBeforeDeploy event) { contextCDIIntegration(event.getContext()); } private void contextCDIIntegration(final WebBeansContext wbc) { if (!enabled) { return; } final BeanManagerImpl beanManagerImpl = wbc.getBeanManagerImpl(); if (!beanManagerImpl.getAdditionalQualifiers().contains(Context.class)) { beanManagerImpl.addAdditionalQualifier(Context.class); } if (!hasBean(beanManagerImpl, SecurityContext.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(SecurityContext.class, ThreadLocalContextManager.SECURITY_CONTEXT)); } if (!hasBean(beanManagerImpl, UriInfo.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(UriInfo.class, ThreadLocalContextManager.URI_INFO)); } if (!hasBean(beanManagerImpl, HttpServletResponse.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(HttpServletResponse.class, ThreadLocalContextManager.HTTP_SERVLET_RESPONSE)); } if (!hasBean(beanManagerImpl, HttpHeaders.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(HttpHeaders.class, ThreadLocalContextManager.HTTP_HEADERS)); } if (!hasBean(beanManagerImpl, Request.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(Request.class, ThreadLocalContextManager.REQUEST)); } if (!hasBean(beanManagerImpl, ServletConfig.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ServletConfig.class, ThreadLocalContextManager.SERVLET_CONFIG)); } if (!hasBean(beanManagerImpl, Providers.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(Providers.class, ThreadLocalContextManager.PROVIDERS)); } if (!hasBean(beanManagerImpl, ContextResolver.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ContextResolver.class, ThreadLocalContextManager.CONTEXT_RESOLVER)); } if (!hasBean(beanManagerImpl, ResourceInfo.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ResourceInfo.class, ThreadLocalContextManager.RESOURCE_INFO)); } if (!hasBean(beanManagerImpl, ResourceContext.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ResourceContext.class, ThreadLocalContextManager.RESOURCE_CONTEXT)); } if (!hasBean(beanManagerImpl, HttpServletRequest.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(HttpServletRequest.class, ThreadLocalContextManager.HTTP_SERVLET_REQUEST)); } if (!hasBean(beanManagerImpl, ServletRequest.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ServletRequest.class, ThreadLocalContextManager.SERVLET_REQUEST)); } if (!hasBean(beanManagerImpl, ServletContext.class)) { beanManagerImpl.addInternalBean(new ContextBean<>(ServletContext.class, ThreadLocalContextManager.SERVLET_CONTEXT)); } beanManagerImpl.getInjectionResolver().clearCaches(); // hasBean() usage can have cached several things } private static boolean hasBean(final BeanManagerImpl beanManagerImpl, final Class<?> type) { return beanManagerImpl.getInjectionResolver().implResolveByType(false, type).isEmpty(); } @Override public void init(final Properties properties) throws Exception { super.init(properties); config = properties; factoryByListener = "true".equalsIgnoreCase(properties.getProperty("openejb.cxf-rs.factoryByListener", "false")); System.setProperty("org.apache.johnzon.max-string-length", SystemInstance.get().getProperty("org.apache.johnzon.max-string-length", properties.getProperty("org.apache.johnzon.max-string-length", "8192"))); SystemInstance.get().setComponent(RESTResourceFinder.class, new CxfRESTResourceFinder()); try { CUTask.addContainerListener(new CUTask.ContainerListener() { @Override public Object onCreation() { return Contexts.state(); } @Override public Object onStart(final Object state) { return Contexts.restore(state); } @Override public void onEnd(final Object oldState) { Contexts.restore(oldState); } }); } catch(final Throwable th) { // unlikely but means the container core has been customized so just ignore it } CxfUtil.configureBus(); final Bus bus = CxfUtil.getBus(); final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(CxfUtil.initBusLoader()); try { // force init of bindings if (!CxfUtil.hasService(JAXRSBindingFactory.JAXRS_BINDING_ID)) { // cxf does it but with the pattern "if not here install it". It is slow so installing it without testing for presence here. final BindingFactoryManager bfm = bus.getExtension(BindingFactoryManager.class); try { bfm.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, new JAXRSBindingFactory(bus)); } catch (Throwable b) { // no-op } } hacksOn(); initCxfProviders(bus); } finally { if (oldLoader != null) { CxfUtil.clearBusLoader(oldLoader); } } } private void initCxfProviders(final Bus bus) { if (bus.getProperty("org.apache.cxf.jaxrs.bus.providers") == null) { bus.setProperty("skip.default.json.provider.registration", "true"); // client jaxrs, we want johnzon not jettison final Collection<Object> defaults = new ArrayList<>(); for (final String provider : asList( "org.apache.openejb.server.cxf.rs.johnzon.TomEEJohnzonProvider", "org.apache.openejb.server.cxf.rs.johnzon.TomEEJsonpProvider")) { if (!isActive(provider)) { continue; } try { defaults.add(Class.forName(provider, true, CxfRSService.class.getClassLoader()).newInstance()); } catch (final Exception e) { // no-op } } try { final List<Object> all; final String userProviders = SystemInstance.get().getProperty("openejb.jaxrs.client.providers"); if (userProviders == null) { (all = new ArrayList<>(defaults.size())).addAll(defaults); } else { all = new ArrayList<>(defaults.size() + 2 /* blind guess */); for (String p : userProviders.split(" *, *")) { p = p.trim(); if (p.isEmpty()) { continue; } all.add(Thread.currentThread().getContextClassLoader().loadClass(p).newInstance()); } // added after to be after in the list once sorted all.addAll(defaults); } bus.setProperty("org.apache.cxf.jaxrs.bus.providers", all); } catch (final Exception e) { throw new IllegalStateException(e); } } } private void hacksOn() { CxfHacks.initCxfClassHelper(); } @Override public void stop() throws ServiceException { super.stop(); CxfUtil.release(); } @Override protected void beforeStart() { super.beforeStart(); destinationFactory = new HTTPTransportFactory(); } @Override protected boolean containsJaxRsConfiguration(final Properties properties) { return properties.containsKey(CxfRsHttpListener.PROVIDERS_KEY) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.IN_FAULT_INTERCEPTORS) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.IN_INTERCEPTORS) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.OUT_FAULT_INTERCEPTORS) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.OUT_INTERCEPTORS) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.DATABINDING) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.FEATURES) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.ADDRESS) || properties.containsKey(CxfRsHttpListener.CXF_JAXRS_PREFIX + CxfUtil.ENDPOINT_PROPERTIES); } @Override protected RsHttpListener createHttpListener() { return new CxfRsHttpListener(!factoryByListener ? destinationFactory : new HTTPTransportFactory(), getWildcard(), this); } public boolean isActive(final String name) { final String key = name + ".activated"; return "true".equalsIgnoreCase(SystemInstance.get().getProperty(key, config.getProperty(key, "true"))); } private static class ContextLiteral extends EmptyAnnotationLiteral<Context> implements Context { private static final long serialVersionUID = 1L; public static final AnnotationLiteral<Context> INSTANCE = new ContextLiteral(); } private static class ContextBean<T> implements Bean<T>, PassivationCapable { private final Class<T> type; private final Set<Type> types; private final Set<Annotation> qualifiers; private final T proxy; private final String id; public ContextBean(final Class<T> type, final AbstractRestThreadLocalProxy<T> proxy) { this.type = type; this.proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{type, Serializable.class}, new DelegateHandler(proxy)); this.types = new HashSet<Type>(asList(Object.class, type)); this.qualifiers = new HashSet<Annotation>(asList(ContextLiteral.INSTANCE, AnyLiteral.INSTANCE)); this.id = ContextBean.class.getName() + "#" + type.getName(); } @Override public String getId() { return id; } @Override public Set<Type> getTypes() { return types; } @Override public Set<Annotation> getQualifiers() { return qualifiers; } @Override public Class<? extends Annotation> getScope() { return ApplicationScoped.class; } @Override public String getName() { return null; } @Override public boolean isNullable() { return false; } @Override public Set<InjectionPoint> getInjectionPoints() { return Collections.emptySet(); } @Override public Class<?> getBeanClass() { return type; } @Override public Set<Class<? extends Annotation>> getStereotypes() { return Collections.emptySet(); } @Override public boolean isAlternative() { return false; } @Override public T create(final CreationalContext<T> tCreationalContext) { return proxy; } @Override public void destroy(final T t, final CreationalContext<T> tCreationalContext) { // no-op } } private static class DelegateHandler<T> implements InvocationHandler { private final AbstractRestThreadLocalProxy<T> proxy; public DelegateHandler(final AbstractRestThreadLocalProxy<T> proxy) { this.proxy = proxy; } @Override public Object invoke(final Object ignored, final Method method, final Object[] args) throws Throwable { try { return method.invoke(proxy.get(), args); } catch (final InvocationTargetException ite) { throw ite.getCause(); } } } }