package org.needle4j.injection; import static org.needle4j.reflection.ReflectionUtil.forName; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import org.needle4j.common.MapEntry; import org.needle4j.configuration.NeedleConfiguration; import org.needle4j.configuration.PropertyBasedConfigurationFactory; import org.needle4j.mock.MockAnnotationProcessor; import org.needle4j.mock.MockProvider; import org.needle4j.mock.SpyProvider; import org.needle4j.postconstruct.PostConstructProcessor; import org.needle4j.predicate.IsSupportedAnnotationPredicate; import org.needle4j.processor.ChainedNeedleProcessor; import org.needle4j.reflection.ReflectionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class InjectionConfiguration { private static final Logger LOG = LoggerFactory.getLogger(InjectionConfiguration.class); private static final Set<Class<?>> POSTCONSTRUCT_CLASSES = ReflectionUtil .getClasses("javax.annotation.PostConstruct"); private static final Class<?> RESOURCE_CLASS = forName("javax.annotation.Resource"); private static final Class<?> INJECT_CLASS = forName("javax.inject.Inject"); private static final Class<?> CDI_INSTANCE_CLASS = forName("javax.enterprise.inject.Instance"); private static final Class<?> EJB_CLASS = forName("javax.ejb.EJB"); private static final Class<?> PERSISTENCE_CONTEXT_CLASS = forName("javax.persistence.PersistenceContext"); private static final Class<?> PERSISTENCE_UNIT_CLASS = forName("javax.persistence.PersistenceUnit"); private final NeedleConfiguration needleConfiguration; // Default InjectionProvider for annotations private final List<InjectionProvider<?>> injectionProviderList = new ArrayList<InjectionProvider<?>>(); // Global InjectionProvider for custom implementation private final List<InjectionProvider<?>> globalInjectionProviderList = new ArrayList<InjectionProvider<?>>(); // Test-specific custom injection provider private final List<InjectionProvider<?>> testInjectionProvider = new ArrayList<InjectionProvider<?>>(); // all with priority order private final List<List<InjectionProvider<?>>> allInjectionProvider; private final Set<Class<? extends Annotation>> injectionAnnotationClasses = new HashSet<Class<? extends Annotation>>(); private final MockProvider mockProvider; private final SpyProvider spyProvider; private final PostConstructProcessor postConstructProcessor; private final InjectionAnnotationProcessor injectionIntoAnnotationProcessor; private final TestcaseInjectionProcessor testcaseInjectionProcessor; private final MockAnnotationProcessor mockAnnotationProcessor; private final ChainedNeedleProcessor chainedNeedleProcessor; /** * @see #InjectionConfiguration(NeedleConfiguration) */ public InjectionConfiguration() { this(PropertyBasedConfigurationFactory.get()); } @SuppressWarnings("unchecked") public InjectionConfiguration(final NeedleConfiguration needleConfiguration) { this.needleConfiguration = needleConfiguration; this.mockProvider = createMockProvider(lookupMockProviderClass(needleConfiguration.getMockProviderClassName())); // use mockprovider if mockprovider supports spies, otherwise Fake // implementation this.spyProvider = (this.mockProvider instanceof SpyProvider) ? (SpyProvider) mockProvider : SpyProvider.FAKE; this.postConstructProcessor = new PostConstructProcessor( POSTCONSTRUCT_CLASSES, needleConfiguration.getPostConstructExecuteStrategy()); this.injectionIntoAnnotationProcessor = new InjectionAnnotationProcessor(new IsSupportedAnnotationPredicate( this)); this.testcaseInjectionProcessor = new TestcaseInjectionProcessor(this); this.mockAnnotationProcessor = new MockAnnotationProcessor(this); this.chainedNeedleProcessor = new ChainedNeedleProcessor(mockAnnotationProcessor, injectionIntoAnnotationProcessor, testcaseInjectionProcessor); addCdiInstance(); add(INJECT_CLASS); add(EJB_CLASS); add(PERSISTENCE_CONTEXT_CLASS); add(PERSISTENCE_UNIT_CLASS); addResource(); initGlobalInjectionAnnotation(); initGlobalInjectionProvider(); injectionProviderList.add(0, new MockProviderInjectionProvider(mockProvider)); allInjectionProvider = Arrays.asList(testInjectionProvider, globalInjectionProviderList, injectionProviderList); } private void addResource() { if (RESOURCE_CLASS != null) { addInjectionAnnotation(RESOURCE_CLASS); injectionProviderList.add(new ResourceMockInjectionProvider(this)); } } private void addCdiInstance() { if (CDI_INSTANCE_CLASS != null) { // addInjectionAnnotation(RESOURCE_CLASS); injectionProviderList.add(new CDIInstanceInjectionProvider(CDI_INSTANCE_CLASS, this)); } } private void add(final Class<?> clazz) { if (clazz != null) { LOG.debug("register injection handler for class {}", clazz); injectionProviderList.add(new DefaultMockInjectionProvider(clazz, this)); addInjectionAnnotation(clazz); } } @SuppressWarnings("unchecked") public <T extends MockProvider> T getMockProvider() { return (T) mockProvider; } public SpyProvider getSpyProvider() { return spyProvider; } public PostConstructProcessor getPostConstructProcessor() { return postConstructProcessor; } public InjectionAnnotationProcessor getInjectionIntoAnnotationProcessor() { return injectionIntoAnnotationProcessor; } public TestcaseInjectionProcessor getTestcaseInjectionProcessor() { return testcaseInjectionProcessor; } public MockAnnotationProcessor getMockAnnotationProcessor() { return mockAnnotationProcessor; } public ChainedNeedleProcessor getChainedNeedleProcessor() { return chainedNeedleProcessor; } public final void addInjectionProvider(final InjectionProvider<?>... injectionProvider) { for (final InjectionProvider<?> provider : injectionProvider) { testInjectionProvider.add(0, provider); } } public List<List<InjectionProvider<?>>> getInjectionProvider() { return allInjectionProvider; } private void initGlobalInjectionAnnotation() { final Set<Class<Annotation>> customInjectionAnnotations = needleConfiguration.getCustomInjectionAnnotations(); addGlobalInjectionAnnotation(customInjectionAnnotations); } public void addGlobalInjectionAnnotation(final Set<Class<Annotation>> customInjectionAnnotations) { for (final Class<? extends Annotation> annotation : customInjectionAnnotations) { addInjectionAnnotation(annotation); globalInjectionProviderList.add(0, new DefaultMockInjectionProvider(annotation, this)); } } private void initGlobalInjectionProvider() { final Set<Class<InjectionProvider<?>>> customInjectionProviders = needleConfiguration .getCustomInjectionProviderClasses(); for (final Class<InjectionProvider<?>> injectionProviderClass : customInjectionProviders) { try { final InjectionProvider<?> injection = ReflectionUtil.createInstance(injectionProviderClass); globalInjectionProviderList.add(0, injection); } catch (final Exception e) { LOG.warn("could not create an instance of injection provider " + injectionProviderClass, e); } } for (final Class<InjectionProviderInstancesSupplier> supplierClass : needleConfiguration .getCustomInjectionProviderInstancesSupplierClasses()) { try { final InjectionProviderInstancesSupplier supplier = ReflectionUtil.createInstance(supplierClass); globalInjectionProviderList.addAll(0, supplier.get()); } catch (final Exception e) { LOG.warn("could not create an instance of injection provider instance supplier " + supplierClass, e); } } } @SuppressWarnings("unchecked") private void addInjectionAnnotation(final Class<?> clazz) { if (clazz.isAnnotation()) { injectionAnnotationClasses.add((Class<? extends Annotation>) clazz); } } public boolean isAnnotationSupported(final Class<? extends Annotation> annotation) { return injectionAnnotationClasses.contains(annotation); } Set<Class<? extends Annotation>> getSupportedAnnotations() { return Collections.unmodifiableSet(injectionAnnotationClasses); } public Entry<Object, Object> handleInjectionProvider(final Collection<InjectionProvider<?>> injectionProviders, final InjectionTargetInformation injectionTargetInformation) { for (final InjectionProvider<?> provider : injectionProviders) { if (provider.verify(injectionTargetInformation)) { final Object object = provider.getInjectedObject(injectionTargetInformation.getType()); final Object key = provider.getKey(injectionTargetInformation); return new MapEntry<Object, Object>(key, object); } } return null; } // TODO extract public static Class<? extends MockProvider> lookupMockProviderClass(final String mockProviderClassName) { if (mockProviderClassName == null) { return autoDetectMockProviderClass(); } try { final Class<MockProvider> mockProviderClass = ReflectionUtil.lookupClass(MockProvider.class, mockProviderClassName); return mockProviderClass; } catch (final Exception e) { throw new RuntimeException("could not load mock provider class: '" + mockProviderClassName + "'", e); } } private static Class<? extends MockProvider> autoDetectMockProviderClass() { List<String> providers = Arrays.asList("org.needle4j.mock.MockitoProvider", "org.needle4j.mock.EasyMockProvider"); for (String p: providers) { try { return ReflectionUtil.lookupClass(MockProvider.class, p); } catch (Exception e) { // ignored. provider not available due to missing dependencies. } } throw new RuntimeException("no mock provider configured"); } @SuppressWarnings("unchecked") <T extends MockProvider> T createMockProvider(final Class<? extends MockProvider> mockProviderClass) { try { return (T) mockProviderClass.newInstance(); } catch (final Exception e) { throw new RuntimeException("could not create a new instance of mock provider " + mockProviderClass, e); } } }