/* * 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.arquillian.common.enrichment; import org.apache.openejb.AppContext; import org.apache.openejb.BeanContext; import org.apache.openejb.InjectionProcessor; import org.apache.openejb.OpenEJBException; import org.apache.openejb.OpenEJBRuntimeException; import org.apache.openejb.arquillian.common.mockito.MockitoEnricher; import org.apache.openejb.core.Operation; import org.apache.openejb.core.ThreadContext; import org.apache.openejb.core.WebContext; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.util.AppFinder; import org.apache.webbeans.annotation.AnyLiteral; import org.apache.webbeans.annotation.DefaultLiteral; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.BeanManagerImpl; import org.apache.webbeans.inject.OWBInjector; import org.jboss.arquillian.test.spi.TestClass; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.AnnotatedCallable; import javax.enterprise.inject.spi.AnnotatedParameter; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionPoint; import java.lang.annotation.Annotation; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import static java.util.Arrays.asList; public final class OpenEJBEnricher { private static final Logger LOGGER = Logger.getLogger(OpenEJBEnricher.class.getName()); private OpenEJBEnricher() { // no-op } public static void enrich(final Object testInstance, final AppContext appCtx) { // don't rely on arquillian since this enrichment should absolutely be done before the following ones new MockitoEnricher().enrich(testInstance); AppContext ctx = appCtx; if (ctx == null) { ctx = AppFinder.findAppContextOrWeb(Thread.currentThread().getContextClassLoader(), AppFinder.AppContextTransformer.INSTANCE); if (ctx == null) { return; } } final BeanContext context = SystemInstance.get().getComponent(ContainerSystem.class).getBeanContext(ctx.getId() + "_" + testInstance.getClass().getName()); final WebBeansContext appWBC = ctx.getWebBeansContext(); final BeanManagerImpl bm = appWBC == null ? null : appWBC.getBeanManagerImpl(); boolean ok = false; for (final WebContext web : ctx.getWebContexts()) { final WebBeansContext webBeansContext = web.getWebBeansContext(); if (webBeansContext == null) { continue; } final BeanManagerImpl webAppBm = webBeansContext.getBeanManagerImpl(); if (webBeansContext != appWBC && webAppBm.isInUse()) { try { doInject(testInstance, context, webAppBm); ok = true; break; } catch (final Exception e) { // no-op, try next } } } if (bm != null && bm.isInUse() && !ok) { try { doInject(testInstance, context, bm); } catch (final Exception e) { LOGGER.log(Level.SEVERE, "Failed injection on: " + testInstance.getClass(), e); if (RuntimeException.class.isInstance(e)) { throw RuntimeException.class.cast(e); } throw new OpenEJBRuntimeException(e); } } if (context != null) { final ThreadContext callContext = new ThreadContext(context, null, Operation.INJECTION); final ThreadContext oldContext = ThreadContext.enter(callContext); try { final InjectionProcessor processor = new InjectionProcessor<>(testInstance, context.getInjections(), context.getJndiContext()); processor.createInstance(); } catch (final OpenEJBException e) { // ignored } finally { ThreadContext.exit(oldContext); } } } private static void doInject(final Object testInstance, final BeanContext context, final BeanManagerImpl bm) throws Exception { final Set<Bean<?>> beans = bm.getBeans(testInstance.getClass()); final Bean<?> bean = bm.resolve(beans); final CreationalContext<?> cc = bm.createCreationalContext(bean); if (context != null) { context.set(CreationalContext.class, cc); } OWBInjector.inject(bm, testInstance, cc); } private static BeanManagerImpl findBeanManager(final AppContext ctx) { if (ctx != null) { if (ctx.getWebBeansContext() == null) { return null; } return ctx.getWebBeansContext().getBeanManagerImpl(); } try { // else try to find it from tccl through our SingletonService return WebBeansContext.currentInstance().getBeanManagerImpl(); } catch (final Exception e) { // if not found IllegalStateException or a NPE can be thrown // no-op } return null; } public static Object[] resolve(final AppContext appContext, final TestClass ignored, final Method method) { // suppose all is a CDI bean... final Object[] values = new Object[method.getParameterTypes().length]; if (appContext == null) { return values; } final List<BeanManager> beanManagers = new ArrayList<>(); final BeanManager bm = findBeanManager(appContext); if (bm != null) { // then add web bean manager first, TODO: selection of the webapp containing the test? for (final WebContext web : appContext.getWebContexts()) { final WebBeansContext webBeansContext = web.getWebBeansContext(); if (webBeansContext == null) { continue; } final BeanManagerImpl webAppBm = webBeansContext.getBeanManagerImpl(); if (bm != webAppBm) { beanManagers.add(webAppBm); } } beanManagers.add(bm); } if (beanManagers.isEmpty()) { return values; } final Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Exception ex = null; for (final BeanManager beanManager : beanManagers) { try { values[i] = getParamInstance(beanManager, i, method); break; } catch (final Exception e) { ex = e; } } if (ex != null) { LOGGER.info(ex.getMessage()); } } return values; } private static <T> T getParamInstance(final BeanManager manager, final int position, final Method method) { final CreationalContext<?> creational = manager.createCreationalContext(null); // TODO: release in @After return (T) manager.getInjectableReference(new MethodParamInjectionPoint(method, position, manager), creational); } private static final class MethodParamInjectionPoint implements InjectionPoint { private final Method method; private final int position; private final Set<Annotation> qualifiers = new HashSet<>(); private MethodParamInjectionPoint(final Method method, final int position, final BeanManager beanManager) { this.method = method; this.position = position; for (final Annotation annotation : method.getParameterAnnotations()[position]) { if (beanManager.isQualifier(annotation.annotationType())) { qualifiers.add(annotation); } } if (qualifiers.isEmpty()) { qualifiers.add(DefaultLiteral.INSTANCE); } qualifiers.add(AnyLiteral.INSTANCE); } @Override public Type getType() { if (method.getGenericParameterTypes().length > 0) { return method.getGenericParameterTypes()[position]; } return method.getParameterTypes()[position]; } @Override public Set<Annotation> getQualifiers() { return qualifiers; } @Override public Bean<?> getBean() { return null; } @Override public Member getMember() { return method; } @Override public Annotated getAnnotated() { return new ParamAnnotated(method, position); } @Override public boolean isDelegate() { return false; } @Override public boolean isTransient() { return false; } } private static final class ParamAnnotated implements AnnotatedParameter<Object> { private final Method method; private final int position; private final Set<Type> types = new HashSet<>(); private final Set<Annotation> annotations; private ParamAnnotated(final Method method, final int position) { this.method = method; this.position = position; types.add(getBaseType()); types.add(Object.class); annotations = new HashSet<Annotation>(asList(method.getParameterAnnotations()[position])); } @Override public int getPosition() { return position; } @Override public AnnotatedCallable<Object> getDeclaringCallable() { return null; } @Override public Type getBaseType() { if (method.getGenericParameterTypes().length > 0) { return method.getGenericParameterTypes()[position]; } return method.getParameterTypes()[position]; } @Override public Set<Type> getTypeClosure() { return types; } @Override public <T extends Annotation> T getAnnotation(final Class<T> annotationType) { for (final Annotation a : annotations) { if (a.annotationType().getName().equals(annotationType.getName())) { return (T) a; } } return null; } @Override public Set<Annotation> getAnnotations() { return annotations; } @Override public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) { return getAnnotation(annotationType) != null; } } }