/* * 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.jaxrs.lifecycle.ResourceProvider; import org.apache.cxf.jaxrs.utils.InjectionUtils; import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.message.Message; import org.apache.openejb.Injection; import org.apache.openejb.InjectionProcessor; import org.apache.openejb.OpenEJBException; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.webbeans.component.InjectionTargetBean; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.BeanManagerImpl; import org.apache.webbeans.inject.OWBInjector; import org.apache.webbeans.intercept.InterceptorResolutionService; import org.apache.webbeans.portable.InjectionTargetImpl; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.InjectionException; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.Decorator; import javax.enterprise.inject.spi.Interceptor; import javax.naming.Context; import javax.naming.InitialContext; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; public abstract class CdiResourceProvider implements ResourceProvider { public static final String INSTANCE_KEY = CdiResourceProvider.class.getName() + ".instance"; protected final Collection<Injection> injections; protected final Context context; protected final WebBeansContext webbeansContext; protected final Class<?> clazz; protected final Method postConstructMethod; protected final Method preDestroyMethod; protected final ClassLoader classLoader; protected final Collection<Class<?>> contextTypes = new HashSet<>(); protected final BeanCreator normalScopeCreator; protected final BeanManagerImpl bm; protected final Bean<?> bean; protected Constructor<?> constructor; public CdiResourceProvider(final ClassLoader loader, final Class<?> clazz, final Collection<Injection> injectionCollection, final Context initialContext, final WebBeansContext owbCtx) { injections = injectionCollection; webbeansContext = owbCtx; classLoader = loader; context = (Context) Proxy.newProxyInstance(classLoader, new Class<?>[]{Context.class}, new InitialContextWrapper(initialContext)); postConstructMethod = ResourceUtils.findPostConstructMethod(clazz); preDestroyMethod = ResourceUtils.findPreDestroyMethod(clazz); bm = webbeansContext == null ? null : webbeansContext.getBeanManagerImpl(); this.clazz = clazz; if (bm != null && bm.isInUse()) { try { final Set<Bean<?>> beans = bm.getBeans(clazz); bean = bm.resolve(beans); } catch (final InjectionException ie) { final String msg = "Resource class " + clazz.getName() + " can not be instantiated"; throw new WebApplicationException(Response.serverError().entity(msg).build()); } if (bean != null && bm.isNormalScope(bean.getScope())) { // singleton is faster normalScopeCreator = new ProvidedInstanceBeanCreator(bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean))); } else { normalScopeCreator = null; validateConstructorExists(); } } else { bean = null; normalScopeCreator = null; validateConstructorExists(); } findContexts(clazz); } private void findContexts(final Class<?> clazz) { if (bean instanceof InjectionTargetBean<?>) { final InterceptorResolutionService.BeanInterceptorInfo info = InjectionTargetImpl.class.cast(InjectionTargetBean.class.cast(bean).getInjectionTarget()).getInterceptorInfo(); for (final Interceptor<?> interceptor : info.getCdiInterceptors()) { if (interceptor == null || interceptor.getBeanClass() == null) { continue; } Contexts.findContextFields(interceptor.getBeanClass(), contextTypes); } for (final Interceptor<?> interceptor : info.getEjbInterceptors()) { if (interceptor == null || interceptor.getBeanClass() == null) { continue; } Contexts.findContextFields(interceptor.getBeanClass(), contextTypes); } for (final Decorator<?> decorator : info.getDecorators()) { if (decorator == null || decorator.getBeanClass() == null) { continue; } Contexts.findContextFields(decorator.getBeanClass(), contextTypes); } } Contexts.findContextFields(clazz, contextTypes); // for the class itself } private void validateConstructorExists() { // only validate it here otherwise we'll fail for CDI injections constructor = ResourceUtils.findResourceConstructor(clazz, true); if (constructor == null) { throw new RuntimeException("Resource class " + clazz + " has no valid constructor"); } } @Override public Object getInstance(final Message m) { final Object existing = m.getExchange().get(INSTANCE_KEY); if (existing != null) { return existing; } Contexts.bind(m.getExchange(), contextTypes); BeanCreator creator; if (bean != null) { if (normalScopeCreator != null) { creator = normalScopeCreator; } else { creator = new PseudoScopedCdiBeanCreator(); } } else { creator = new DefaultBeanCreator(m, constructor); } m.put(BeanCreator.class, creator); m.put(CdiResourceProvider.class, this); // important to switch of classloader to get the right InitialContext final Thread thread = Thread.currentThread(); final ClassLoader oldLoader = thread.getContextClassLoader(); thread.setContextClassLoader(classLoader); Object instance; try { instance = creator.create(); } catch (final NoBeanFoundException nbfe) { creator = new DefaultBeanCreator(m, constructor); m.put(BeanCreator.class, creator); instance = creator.create(); } finally { thread.setContextClassLoader(oldLoader); } m.getExchange().put(INSTANCE_KEY, instance); return instance; } @Override // this method is not linked to o to consider it stateless public void releaseInstance(final Message m, final Object o) { final BeanCreator c = m.get(BeanCreator.class); if (c != null) { c.release(); } } @Override public Class<?> getResourceClass() { return clazz; } @Override public boolean isSingleton() { return false; } public ClassLoader getClassLoader() { return classLoader; } private static class InitialContextWrapper implements InvocationHandler { private final Context ctx; public InitialContextWrapper(final Context initialContext) { ctx = initialContext; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (method.getName().equals("lookup")) { final String name = "java:" + String.class.cast(args[0]); if (args[0].getClass().equals(String.class)) { // Note: we catch exception instead of namingexception // because in environment with proxies on Context // InvocationtargetException can be thrown instead of NamingException if (ctx != null) { try { return ctx.lookup(name); } catch (final Exception ne) { try { return ctx.lookup(String.class.cast(args[0])); } catch (final Exception ignored) { // no-op } } } final Context initialContext = new InitialContext(); try { return initialContext.lookup(name); } catch (final Exception swallowed) { try { return initialContext.lookup(String.class.cast(args[0])); } catch (final Exception ignored) { // no-op } } } } try { return method.invoke(ctx, args); } catch (final InvocationTargetException ite) { throw ite.getCause(); } } } protected interface BeanCreator { Object create(); void release(); } protected class ProvidedInstanceBeanCreator implements BeanCreator { private final Object instance; protected ProvidedInstanceBeanCreator(final Object instance) { this.instance = instance; } @Override public Object create() { return instance; } @Override public void release() { // no-op } } protected class PseudoScopedCdiBeanCreator implements BeanCreator { private CreationalContext<?> toClean = null; @Override public Object create() { try { toClean = bm.createCreationalContext(bean); return bm.getReference(bean, bean.getBeanClass(), toClean); } catch (final InjectionException ie) { final String msg = "Failed to instantiate: " + bean; Logger.getInstance(LogCategory.OPENEJB_CDI, this.getClass()).error(msg, ie); throw new WebApplicationException(Response.serverError().entity(msg).build()); } } @Override public void release() { toClean.release(); toClean = null; } } protected class DefaultBeanCreator implements BeanCreator { private final Message m; private final Constructor<?> constructor; private InjectionProcessor<?> injector; private CreationalContext creationalContext; private Object instance; public DefaultBeanCreator(final Message m, final Constructor<?> constructor) { this.m = m; this.constructor = constructor; } protected Object newInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException { return constructor.newInstance(ResourceUtils.createConstructorArguments(constructor, m, m != null)); } @Override public Object create() { try { instance = newInstance(); doInit(); return instance; } catch (final InstantiationException ex) { final String msg = "Resource class " + constructor.getDeclaringClass().getName() + " can not be instantiated"; throw new WebApplicationException(Response.serverError().entity(msg).build()); } catch (final IllegalAccessException ex) { final String msg = "Resource class " + constructor.getDeclaringClass().getName() + " can not be instantiated" + " due to IllegalAccessException"; throw new WebApplicationException(Response.serverError().entity(msg).build()); } catch (final InvocationTargetException ex) { final String msg = "Resource class " + constructor.getDeclaringClass().getName() + " can not be instantiated" + " due to InvocationTargetException"; throw new WebApplicationException(Response.serverError().entity(msg).build()); } catch (final OpenEJBException e) { final String msg = "An error occured injecting in class " + constructor.getDeclaringClass().getName(); throw new WebApplicationException(Response.serverError().entity(msg).build()); } } protected void doInit() throws OpenEJBException { injector = new InjectionProcessor<>(instance, new ArrayList<>(injections), InjectionProcessor.unwrap(context)); instance = injector.createInstance(); final BeanManager bm = webbeansContext == null ? null : webbeansContext.getBeanManagerImpl(); if (bm != null) { creationalContext = bm.createCreationalContext(null); try { OWBInjector.inject(bm, instance, creationalContext); } catch (final Exception e) { // ignored } } // injector.postConstruct(); // it doesn't know it InjectionUtils.invokeLifeCycleMethod(instance, postConstructMethod); } @Override public void release() { // we can't give it to the injector so let's do it manually try { InjectionUtils.invokeLifeCycleMethod(instance, preDestroyMethod); } finally { if (injector != null) { injector.preDestroy(); } if (creationalContext != null) { creationalContext.release(); } } } } private static class NoBeanFoundException extends RuntimeException { public NoBeanFoundException(final String name) { super(name); } } }