All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.weld.junit.AbstractWeldInitiator Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2017, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed 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.jboss.weld.junit;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import jakarta.annotation.Resource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.NormalScope;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.enterprise.inject.spi.InjectionTarget;
import jakarta.enterprise.util.TypeLiteral;

import org.jboss.weld.config.ConfigurationKey;
import org.jboss.weld.environment.ContainerInstance;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.jboss.weld.inject.WeldInstance;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 *
 * @author Matej Novotny
 */
public abstract class AbstractWeldInitiator implements Instance, ContainerInstance {

    /**
     * The returned {@link Weld} instance has:
     * 
    *
  • automatic discovery disabled
  • *
  • concurrent deployment disabled
  • *
* * @return a new {@link Weld} instance suitable for testing */ public static Weld createWeld() { return new Weld().disableDiscovery().property(ConfigurationKey.CONCURRENT_DEPLOYMENT.get(), false); } protected final Weld weld; protected final List instancesToInject; protected final Set> scopesToActivate; protected final Set> beans; protected final WeldCDIExtension extension; private final Map resources; private final Function ejbFactory; private final Function persistenceUnitFactory; private final Function persistenceContextFactory; protected volatile WeldContainer container; protected AbstractWeldInitiator(Weld weld, List instancesToInject, Set> scopesToActivate, Set> beans, Map resources, Function ejbFactory, Function persistenceUnitFactory, Function persistenceContextFactory) { this.instancesToInject = new ArrayList<>(); for (Object instance : instancesToInject) { this.instancesToInject.add(createToInject(instance)); } this.scopesToActivate = scopesToActivate; this.beans = beans; this.weld = weld; boolean hasMockInterceptor = false; boolean dummyBeanAdded = false; if (hasScopesToActivate() || hasBeansToAdd()) { this.extension = new WeldCDIExtension(this.scopesToActivate, this.beans); for (Bean bean : this.beans) { if (bean instanceof MockBean) { MockBean mockBean = (MockBean) bean; if (mockBean.isAlternative() && mockBean.isSelectForSyntheticBeanArchive()) { this.weld.addAlternative(mockBean.getBeanClass()); if (!dummyBeanAdded) { // by adding a dummy bean we make sure that synthetic archive gets created even if // the enabled alternative was the only bean added this.weld.addBeanClass(Object.class); dummyBeanAdded = true; } } } else if (bean instanceof MockInterceptor && ((MockInterceptor) bean).hasDefaultBeanClass()) { hasMockInterceptor = true; } } // Automatically enable all mock interceptors for the synthetic bean archive if (hasMockInterceptor) { this.weld.addInterceptor(MockInterceptor.class); } this.weld.addExtension(this.extension); } else { this.extension = null; } this.resources = resources; this.ejbFactory = ejbFactory; this.persistenceContextFactory = persistenceContextFactory; this.persistenceUnitFactory = persistenceUnitFactory; } protected ToInject createToInject(Object instanceToInject) { return new ToInject(instanceToInject); } /** * Injects the given non-contextual instance immediately. The returned {@link AutoCloseable} should be used * to release the creational context once the injected beans are no longer needed. * *

* Example: * *

{@code
     * try (AutoCloseable contextReleaser = injectNonContextual(this)) {
     *     // do some things with the injected instances
     * }
     * }
* * @param target the target to inject * @return an {@code AutoCloseable} to release the creational context */ public AutoCloseable injectNonContextual(Object target) { ToInject toInject = new ToInject(target); toInject.inject(); return toInject::release; } @Override public Iterator iterator() { checkContainer(); return container.iterator(); } @Override public Object get() { checkContainer(); return container.get(); } @Override public WeldInstance select(Annotation... qualifiers) { checkContainer(); return container.select(qualifiers); } @Override public WeldInstance select(Class subtype, Annotation... qualifiers) { checkContainer(); return container.select(subtype, qualifiers); } @Override public WeldInstance select(TypeLiteral subtype, Annotation... qualifiers) { checkContainer(); return container.select(subtype, qualifiers); } @Override public boolean isUnsatisfied() { checkContainer(); return container.isUnsatisfied(); } @Override public boolean isAmbiguous() { checkContainer(); return container.isAmbiguous(); } @Override public void destroy(Object instance) { checkContainer(); container.destroy(instance); } @Override public Handle getHandle() { checkContainer(); return container.getHandle(); } @Override public Iterable> handles() { checkContainer(); return container.handles(); } /** * Allows to fire events. * * @return an event object */ @SuppressWarnings("unchecked") public Event event() { checkContainer(); try { // We need to use reflection due to some compatibility issues Method eventMethod = container.getClass().getMethod("event"); return (Event) eventMethod.invoke(container); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new IllegalStateException("Cannot invoke WeldContainer.event() method using reflection", e); } } @Override public BeanManager getBeanManager() { checkContainer(); return container.getBeanManager(); } @Override public String getId() { return container.getId(); } /** * Note that any container-based operation will result in {@link IllegalStateException} after shutdown. */ @Override public void shutdown() { container.shutdown(); } /** * * @return true if the container was initialized completely and is not shut down yet, false * otherwise */ public boolean isRunning() { return (container != null) && container.isRunning(); } /** * This method should be used when a Weld-specific API is needed. * * @return the underlying container instance */ public WeldContainer container() { checkContainer(); return container; } private void checkContainer() { if (container == null || !container.isRunning()) { throw new IllegalStateException("Weld container is not running"); } } protected void injectInstances() { if (instancesToInject != null) { for (ToInject toInject : instancesToInject) { toInject.inject(); } } } protected void releaseInstances() { if (instancesToInject != null) { for (ToInject toInject : instancesToInject) { toInject.release(); } } } private boolean hasScopesToActivate() { return scopesToActivate != null && !scopesToActivate.isEmpty(); } private boolean hasBeansToAdd() { return beans != null && !beans.isEmpty(); } protected class ToInject { private final Object instance; private volatile CreationalContext creationalContext; ToInject(Object instance) { this.instance = instance; } void inject() { BeanManager beanManager = container.getBeanManager(); CreationalContext ctx = beanManager.createCreationalContext(null); @SuppressWarnings("unchecked") InjectionTarget injectionTarget = (InjectionTarget) beanManager .getInjectionTargetFactory(beanManager.createAnnotatedType(instance.getClass())) .createInjectionTarget(null); injectionTarget.inject(instance, ctx); creationalContext = ctx; } void release() { if (creationalContext != null) { creationalContext.release(); } } } @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Weld object reference is intentionally externally mutable") protected static abstract class AbstractBuilder> { protected final Weld weld; protected final List instancesToInject; protected final Set> scopesToActivate; protected final Set> beans; protected final Map resources; private Function ejbFactory; private Function persistenceUnitFactory; private Function persistenceContextFactory; public AbstractBuilder(Weld weld) { this.weld = weld; this.instancesToInject = new ArrayList<>(); this.scopesToActivate = new HashSet<>(); this.beans = new HashSet<>(); this.resources = new HashMap<>(); } /** * Activate and deactivate contexts for the given normal scopes for the lifetime of the initialized Weld container, by * default for each test method * execution. *

* {@link ApplicationScoped} is ignored as it is always active. *

* * @param normalScopes * @return self */ @SafeVarargs public final T activate(Class... normalScopes) { for (Class scope : normalScopes) { if (ApplicationScoped.class.equals(scope)) { continue; } if (!scope.isAnnotationPresent(NormalScope.class)) { throw new IllegalArgumentException("Only annotations annotated with @NormalScope are supported!"); } this.scopesToActivate.add(scope); } return self(); } protected Function getEjbFactory() { return ejbFactory; } protected Function getPersistenceContextFactory() { return persistenceContextFactory; } protected Function getPersistenceUnitFactory() { return persistenceUnitFactory; } /** * Instructs the initiator to inject the given non-contextual instance once the container is started, i.e. during test * execution. * *

* This method could be used e.g. to inject a test class instance: *

* *
         * public class InjectTest {
         *
         *     @Rule
         *     public WeldInitiator weld = WeldInitiator.fromTestPackage().inject(this).build();
         *
         *     @Inject
         *     Foo foo;
         *
         *     @Test
         *     public void testFoo() {
         *         assertEquals("foo", foo.getId());
         *     }
         * }
         * 
* *

* Injected {@link Dependent} bean instances are destroyed after the test execution. However, the lifecycle of the * non-contextual instance is not * managed by the container and all injected references will be invalid after the test execution. *

* * @param instance * @return self */ public T inject(Object instance) { this.instancesToInject.add(instance); return self(); } /** * Instructs the initiator to add the specified beans during {@link AfterBeanDiscovery} notification. * * @param beans * @return self * @see AfterBeanDiscovery#addBean(Bean) * @see MockBean * @see MockInterceptor * @since 1.1 */ public T addBeans(Bean... beans) { Collections.addAll(this.beans, beans); return self(); } /** * Binds a name to an object. This allows to mock {@link Resource} injection points easily, e.g.: * *
         * @Dependent
         * class Foo {
         *
         *     @Resource(lookup = "bar")
         *     String bar;
         * }
         * 
* * @param name * @param resource * @return self * @since 1.2 */ public T bindResource(String name, Object resource) { resources.put(name, resource); return self(); } /** * Makes it possible to mock {@code @EJB} injection points. * *

* Note that for Weld 3 {@code org.jboss.weld.module:weld-ejb} dependency is also required. *

* * @param ejbFactory * @return self * @since 1.2 */ public T setEjbFactory(Function ejbFactory) { this.ejbFactory = ejbFactory; return self(); } /** * Makes it possible to mock {@code PersistenceUnit} injection points. * * @param persistenceUnitFactory * @return self * @since 1.2 */ public T setPersistenceUnitFactory(Function persistenceUnitFactory) { this.persistenceUnitFactory = persistenceUnitFactory; return self(); } /** * Makes it possible to mock {@code PersistenceContext} injection points. * * @param persistenceContextFactory * @return self * @since 1.2 */ public T setPersistenceContextFactory(Function persistenceContextFactory) { this.persistenceContextFactory = persistenceContextFactory; return self(); } protected abstract T self(); protected abstract I build(Weld weld, List instancesToInject, Set> scopesToActivate, Set> beans); /** * * @return a new initiator instance */ public I build() { return build(weld, instancesToInject.isEmpty() ? Collections.emptyList() : new ArrayList<>(instancesToInject), scopesToActivate.isEmpty() ? Collections.> emptySet() : new HashSet<>(scopesToActivate), beans.isEmpty() ? Collections.> emptySet() : new HashSet<>(beans)); } } protected WeldContainer initWeldContainer(Weld weld) { // Register mock injection services if needed if (!resources.isEmpty()) { weld.addServices(new MockResourceInjectionServices(resources)); } if (ejbFactory != null) { weld.addServices(new MockEjbInjectionServices(ejbFactory)); } if (persistenceContextFactory != null || persistenceUnitFactory != null) { weld.addServices(new MockJpaInjectionServices(persistenceUnitFactory, persistenceContextFactory)); } // Init the container container = weld.initialize(); if (extension != null) { extension.activateContexts(); } injectInstances(); return container; } protected void shutdownWeldContainer() { try { if (extension != null) { extension.deactivateContexts(); } releaseInstances(); } finally { if (container != null && container.isRunning()) { container.shutdown(); } } } }