package com.softwaremill.common.cdi.autofactory.extension; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import org.jboss.weld.manager.BeanManagerImpl; import org.jboss.weld.manager.SimpleInjectionTarget; import org.jboss.weld.resources.ClassTransformer; import com.softwaremill.common.cdi.autofactory.CreatedWith; import com.softwaremill.common.cdi.autofactory.extension.parameter.ParameterValue; import com.softwaremill.common.cdi.autofactory.extension.parameter.converter.ConstructorToParameterValuesConverter; import com.softwaremill.common.cdi.autofactory.extension.parameter.converter.FactoryParameterOnlyConstructorConverter; import com.softwaremill.common.cdi.autofactory.extension.parameter.converter.MixedConstructorConverter; import javax.annotation.Nullable; import javax.enterprise.inject.spi.*; import javax.inject.Inject; import java.lang.reflect.Method; import java.util.Collection; import java.util.Set; import static com.google.common.collect.Collections2.filter; import static com.google.common.collect.Iterables.concat; /** * @author Adam Warski (adam at warski dot org) */ public class AutoFactoryFromCreatedWithCreator<T> { private final BeanManager beanManager; private final AnnotatedType<T> createdType; private final Class<?> factoryClass; public AutoFactoryFromCreatedWithCreator(BeanManager beanManager, CreatedWith createdWithAnnotation, ProcessAnnotatedType<T> event) { this.beanManager = beanManager; this.createdType = event.getAnnotatedType(); this.factoryClass = createdWithAnnotation.value(); } public Bean<T> create() { checkFactoryClassIsAnInterface(); Method soleFactoryMethod = getSoleFactoryMethod(); AnnotatedConstructor<T> soleCreatedTypeConstructor = getSoleAcceptableConstructorOfCreatedType(); boolean constructorInjection = isConstructorInjection(soleCreatedTypeConstructor); MethodParameterIndexer methodParameterIndexer = new MethodParameterIndexer(soleFactoryMethod); ConstructorToParameterValuesConverter converter = getConverter(soleCreatedTypeConstructor, methodParameterIndexer, constructorInjection); ParameterValue[] createdTypeConstructorParameterValues = converter.convert(); // We cannot use BeanManager.createInjectionTarget as this adds the injection target to validation InjectionTarget<T> injectionTarget = createInjectionTargetWithoutValidation(); return new AutoFactoryBean<T>(beanManager, factoryClass, new CreatedTypeData<T>(createdTypeConstructorParameterValues, soleCreatedTypeConstructor, injectionTarget, constructorInjection)); } private SimpleInjectionTarget<T> createInjectionTargetWithoutValidation() { BeanManagerImpl bmi = (BeanManagerImpl) beanManager; return new SimpleInjectionTarget<T>(bmi.getServices().get(ClassTransformer.class).loadClass(createdType), bmi); } private void checkFactoryClassIsAnInterface() { if (!factoryClass.isInterface()) { throw new RuntimeException("A factory class " + factoryClass + "specified in the argument of @CreatedWith for " + createdType + " is not an interface. "); } } private Method getSoleFactoryMethod() { if (factoryClass.getMethods().length != 1) { throw new RuntimeException("A factory class (" + factoryClass + ") can have only one method. " + "Used through @CreatedWith by: " + createdType); } return factoryClass.getMethods()[0]; } private AnnotatedConstructor<T> getSoleAcceptableConstructorOfCreatedType() { /* Yup, I know, imperatively it would've been shorter */ final Predicate<AnnotatedConstructor<T>> diConstructorPredicate = new Predicate<AnnotatedConstructor<T>>() { @Override public boolean apply(@Nullable AnnotatedConstructor<T> input) { return input.getJavaMember().getAnnotation(Inject.class) != null; } }; Set<? extends AnnotatedConstructor<T>> allConstructors = createdType.getConstructors(); Collection<? extends AnnotatedConstructor<T>> diConstructors = filter(allConstructors, diConstructorPredicate); if (diConstructors.size() > 1 || (allConstructors.size() > 1 && diConstructors.size() == 0)) { throw new RuntimeException("A bean created with an auto-factory " + "can have only one constructor: " + createdType); } return Iterables.get(concat(diConstructors, allConstructors), 0); } private ConstructorToParameterValuesConverter getConverter(AnnotatedConstructor<?> soleCreatedTypeConstructor, MethodParameterIndexer methodParameterIndexer, boolean constructorInjection) { if (constructorInjection) { return new MixedConstructorConverter(new QualifierAnnotationsFilter(beanManager), soleCreatedTypeConstructor, methodParameterIndexer); } else { // If the constructor isn't annotated with @Inject, the no dependencies are injected, all parameters // are taken from the factory method. return new FactoryParameterOnlyConstructorConverter(soleCreatedTypeConstructor, methodParameterIndexer); } } private boolean isConstructorInjection(AnnotatedConstructor<T> soleCreatedTypeConstructor) { return soleCreatedTypeConstructor.getAnnotation(Inject.class) != null; } }