From d0ad1b0c99f9660ad37e01e337818d087b021809 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 17 Oct 2018 13:51:48 +0200 Subject: [PATCH] Arc - add basic BeanManager support - a lot of frameworks still rely on BeanManager's getBeans()/createCreationalContext()/getReference() combo at runtime - resolves #57 --- .../protean/arc/processor/BeanGenerator.java | 80 ++++-- .../jboss/protean/arc/processor/BeanInfo.java | 8 + .../jboss/protean/arc/processor/Beans.java | 20 +- .../arc/processor/InterceptorGenerator.java | 6 +- .../arc/processor/InterceptorInfo.java | 2 +- .../arc/processor/MethodDescriptors.java | 3 + ext/arc/runtime/pom.xml | 6 - .../jboss/protean/arc/ArcContainerImpl.java | 118 ++++++-- .../jboss/protean/arc/BeanManagerImpl.java | 261 ++++++++++++++++++ .../protean/arc/BeanManagerProvider.java | 5 +- .../protean/arc/InitializedInterceptor.java | 5 + .../org/jboss/protean/arc/InjectableBean.java | 35 ++- .../org/jboss/protean/arc/InstanceImpl.java | 9 +- .../arc/test/beanmanager/BeanManagerTest.java | 116 ++++++++ 14 files changed, 613 insertions(+), 61 deletions(-) create mode 100644 ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerImpl.java create mode 100644 ext/arc/tests/src/test/java/org/jboss/protean/arc/test/beanmanager/BeanManagerTest.java diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java index bb1e55ecbdd3f..142ef565f23c3 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java @@ -75,7 +75,11 @@ public class BeanGenerator extends AbstractGenerator { private static final AtomicInteger PRODUCER_INDEX = new AtomicInteger(); - private static final String FIELD_NAME_DECLARING_PROVIDER = "declaringProvider"; + protected static final String FIELD_NAME_DECLARING_PROVIDER = "declaringProvider"; + protected static final String FIELD_NAME_BEAN_TYPES = "types"; + protected static final String FIELD_NAME_QUALIFIERS = "qualifiers"; + protected static final String FIELD_NAME_STEREOTYPES = "stereotypes"; + protected static final String FIELD_NAME_PROXY = "proxy"; protected final AnnotationLiteralProcessor annotationLiterals; @@ -121,14 +125,18 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { - qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } if (bean.getScope().isNormal()) { // For normal scopes a client proxy is generated too - beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + beanCreator.getFieldCreator(FIELD_NAME_PROXY, LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + FieldCreator stereotypes = null; + if (!bean.getStereotypes().isEmpty()) { + stereotypes = beanCreator.getFieldCreator(FIELD_NAME_STEREOTYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } Map injectionPointToProviderField = new HashMap<>(); @@ -155,6 +163,10 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle if (bean.isAlternative()) { implementGetAlternativePriority(bean, beanCreator); } + if (stereotypes != null) { + implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); + } + implementGetBeanClass(bean, beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -183,14 +195,18 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { - qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } if (bean.getScope().isNormal()) { // For normal scopes a client proxy is generated too - beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + beanCreator.getFieldCreator(FIELD_NAME_PROXY, LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + FieldCreator stereotypes = null; + if (!bean.getStereotypes().isEmpty()) { + stereotypes = beanCreator.getFieldCreator(FIELD_NAME_STEREOTYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } Map injectionPointToProviderField = new HashMap<>(); @@ -215,6 +231,10 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc implementGetQualifiers(bean, beanCreator, qualifiers.getFieldDescriptor()); } implementGetDeclaringBean(beanCreator); + if (stereotypes != null) { + implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); + } + implementGetBeanClass(bean, beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -243,14 +263,18 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { - qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } if (bean.getScope().isNormal()) { // For normal scopes a client proxy is generated too - beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + beanCreator.getFieldCreator(FIELD_NAME_PROXY, LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + FieldCreator stereotypes = null; + if (!bean.getStereotypes().isEmpty()) { + stereotypes = beanCreator.getFieldCreator(FIELD_NAME_STEREOTYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } createProviderFields(beanCreator, bean, Collections.emptyMap(), Collections.emptyMap()); @@ -271,6 +295,10 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer implementGetQualifiers(bean, beanCreator, qualifiers.getFieldDescriptor()); } implementGetDeclaringBean(beanCreator); + if (stereotypes != null) { + implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); + } + implementGetBeanClass(bean, beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -410,9 +438,8 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be for (org.jboss.jandex.Type type : bean.getTypes()) { constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, typesHandle, Types.getTypeHandle(constructor, type)); } - ResultHandle unmodifiableTypesHandle = constructor - .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), typesHandle); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "beanTypes", Set.class.getName()), constructor.getThis(), + ResultHandle unmodifiableTypesHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, typesHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_BEAN_TYPES, Set.class.getName()), constructor.getThis(), unmodifiableTypesHandle); // Qualifiers @@ -433,12 +460,23 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be constructor.newInstance(MethodDescriptor.ofConstructor(annotationLiteralName))); } } - ResultHandle unmodifiableQualifiersHandle = constructor - .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), qualifiersHandle); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "qualifiers", Set.class.getName()), constructor.getThis(), + ResultHandle unmodifiableQualifiersHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, qualifiersHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_QUALIFIERS, Set.class.getName()), constructor.getThis(), unmodifiableQualifiersHandle); } + // Stereotypes + if (!bean.getStereotypes().isEmpty()) { + ResultHandle stereotypesHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (StereotypeInfo stereotype : bean.getStereotypes()) { + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, stereotypesHandle, + constructor.loadClass(stereotype.getTarget().name().toString())); + } + ResultHandle unmodifiableStereotypesHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, stereotypesHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_STEREOTYPES, Set.class.getName()), constructor.getThis(), + unmodifiableStereotypesHandle); + } + if (bean.getScope().isNormal()) { // this.proxy = new LazyValue(() -> new Bar_ClientProxy(this)) String proxyTypeName = ClientProxyGenerator.getProxyPackageName(bean) + "." + baseName + ClientProxyGenerator.CLIENT_PROXY_SUFFIX; @@ -1066,4 +1104,14 @@ protected void implementGetAlternativePriority(BeanInfo bean, ClassCreator beanC getAlternativePriority.load(bean.getAlternativePriority()))); } + protected void implementGetStereotypes(BeanInfo bean, ClassCreator beanCreator, FieldDescriptor stereotypesField) { + MethodCreator getStereotypes = beanCreator.getMethodCreator("getStereotypes", Set.class).setModifiers(ACC_PUBLIC); + getStereotypes.returnValue(getStereotypes.readInstanceField(stereotypesField, getStereotypes.getThis())); + } + + protected void implementGetBeanClass(BeanInfo bean, ClassCreator beanCreator) { + MethodCreator getBeanClass = beanCreator.getMethodCreator("getBeanClass", Class.class).setModifiers(ACC_PUBLIC); + getBeanClass.returnValue(getBeanClass.loadClass(bean.getBeanClass().toString())); + } + } diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java index 9cb7599d29a58..04eb2f37b0b08 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java @@ -18,6 +18,7 @@ import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.protean.arc.processor.Methods.MethodKey; @@ -100,6 +101,13 @@ boolean isProducerField() { return Kind.FIELD.equals(target.kind()); } + DotName getBeanClass() { + if (declaringBean != null) { + return declaringBean.target.asClass().name(); + } + return target.asClass().name(); + } + boolean isInterceptor() { return false; } diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java index 1819e1487d9a1..f2e19decbc353 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java @@ -236,7 +236,7 @@ static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, Inje resolved.add(b); } } - BeanInfo selected; + BeanInfo selected = null; if (resolved.isEmpty()) { throw new UnsatisfiedResolutionException(injectionPoint + " on " + bean); } else if (resolved.size() > 1) { @@ -251,9 +251,19 @@ static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, Inje if (resolvedAmbiguity.size() == 1) { selected = resolvedAmbiguity.get(0); } else if (resolvedAmbiguity.size() > 1) { + // Keep only the highest priorities resolvedAmbiguity.sort(Beans::compareAlternativeBeans); - selected = resolvedAmbiguity.get(0); - } else { + Integer highest = getAlternativePriority(resolvedAmbiguity.get(0)); + for (Iterator iterator = resolvedAmbiguity.iterator(); iterator.hasNext();) { + if (!highest.equals(getAlternativePriority(iterator.next()))) { + iterator.remove(); + } + } + if (resolved.size() == 1) { + selected = resolvedAmbiguity.get(0); + } + } + if (selected == null) { throw new AmbiguousResolutionException( injectionPoint + " on " + bean + "\nBeans:\n" + resolved.stream().map(Object::toString).collect(Collectors.joining("\n"))); } @@ -263,6 +273,10 @@ static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, Inje injectionPoint.resolve(selected); } + private static Integer getAlternativePriority(BeanInfo bean) { + return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() : bean.getAlternativePriority(); + } + private static int compareAlternativeBeans(BeanInfo bean1, BeanInfo bean2) { // The highest priority wins Integer priority2 = bean2.getDeclaringBean() != null ? bean2.getDeclaringBean().getAlternativePriority() : bean2.getAlternativePriority(); diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java index 30a2a43f533d7..d992d9aac0310 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java @@ -40,6 +40,8 @@ public class InterceptorGenerator extends BeanGenerator { private static final Logger LOGGER = Logger.getLogger(InterceptorGenerator.class); + protected static final String FIELD_NAME_BINDINGS = "bindings"; + /** * * @param annotationLiterals @@ -76,8 +78,8 @@ Collection generate(InterceptorInfo interceptor, ReflectionRegistratio .build(); // Fields - FieldCreator beanTypes = interceptorCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); - FieldCreator bindings = interceptorCreator.getFieldCreator("bindings", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = interceptorCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator bindings = interceptorCreator.getFieldCreator(FIELD_NAME_BINDINGS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); Map injectionPointToProviderField = new HashMap<>(); Map interceptorToProviderField = new HashMap<>(); diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java index 2593c0b11d3f2..8d77b33c6a3a7 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java @@ -40,7 +40,7 @@ class InterceptorInfo extends BeanInfo implements Comparable { */ InterceptorInfo(AnnotationTarget target, BeanDeployment beanDeployment, Set bindings, List injections, int priority) { super(target, beanDeployment, ScopeInfo.DEPENDENT, Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, - null, null, null, null); + null, null, null, Collections.emptyList()); this.bindings = bindings; this.priority = priority; this.aroundInvoke = target.asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.AROUND_INVOKE)).findAny().orElse(null); diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java index 98ddc8ae62986..8046ddb6c60b8 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java @@ -2,6 +2,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -91,6 +92,8 @@ final class MethodDescriptors { static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod(CreationalContextImpl.class, "addDependencyToParent", void.class, InjectableBean.class, Object.class, CreationalContext.class); + static final MethodDescriptor COLLECTIONS_UNMODIFIABLE_SET = MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class); + private MethodDescriptors() { } diff --git a/ext/arc/runtime/pom.xml b/ext/arc/runtime/pom.xml index 2ce50e903b756..5f66899d3891a 100644 --- a/ext/arc/runtime/pom.xml +++ b/ext/arc/runtime/pom.xml @@ -17,12 +17,6 @@ javax.enterprise cdi-api - - - javax.el - javax.el-api - - diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java index ad92ac9c7939a..235bd1592eaea 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java @@ -21,8 +21,10 @@ import javax.enterprise.context.Dependent; import javax.enterprise.context.Initialized; import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.AmbiguousResolutionException; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.util.TypeLiteral; import javax.inject.Singleton; @@ -43,7 +45,7 @@ class ArcContainerImpl implements ArcContainer { private final Map, InjectableContext> contexts; - private final ComputingCache>> resolved; + private final ComputingCache>> resolved; private final List resourceProviders; @@ -168,13 +170,12 @@ InstanceHandle getResource(Type type, Set annotations) { } private InstanceHandle instanceHandle(Type type, Annotation... qualifiers) { - return instance(getBean(type, qualifiers)); + return beanInstanceHandle(getBean(type, qualifiers), null); } - private InstanceHandle instance(InjectableBean bean) { + InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { if (bean != null) { - CreationalContextImpl parentContext = null; - if (Dependent.class.equals(bean.getScope())) { + if (parentContext == null && Dependent.class.equals(bean.getScope())) { parentContext = new CreationalContextImpl<>(); } CreationalContextImpl creationalContext = parentContext != null ? parentContext.child() : new CreationalContextImpl<>(); @@ -199,30 +200,98 @@ private InjectableBean getBean(Type requiredType, Annotation... qualifier if (qualifiers == null || qualifiers.length == 0) { qualifiers = new Annotation[] { Default.Literal.INSTANCE }; } - List> resolvedBeans = resolved.getValue(new Resolvable(requiredType, qualifiers)); - return resolvedBeans.isEmpty() ? null : (InjectableBean) resolvedBeans.get(0); + Set> resolvedBeans = resolved.getValue(new Resolvable(requiredType, qualifiers)); + return resolvedBeans.isEmpty() || resolvedBeans.size() > 1 ? null : (InjectableBean) resolvedBeans.iterator().next(); } - private List> resolve(Resolvable resolvable) { - List> resolvedBeans = new ArrayList<>(); - for (InjectableBean bean : beans) { - if (matches(bean, resolvable.requiredType, resolvable.qualifiers)) { - resolvedBeans.add(bean); + Set> getBeans(Type requiredType, Annotation... qualifiers) { + // This method does not cache the results + return new HashSet<>(getMatchingBeans(new Resolvable(requiredType, qualifiers))); + } + + @SuppressWarnings("unchecked") + Bean resolve(Set> beans) { + if (beans == null || beans.isEmpty()) { + return null; + } else if (beans.size() == 1) { + return beans.iterator().next(); + } else { + // Try to resolve the ambiguity + if (beans.stream().allMatch(b -> b instanceof InjectableBean)) { + List> matching = new ArrayList<>(); + for (Bean bean : beans) { + matching.add((InjectableBean) bean); + } + Set> resolved = resolve(matching); + if (resolved.size() != 1) { + throw new AmbiguousResolutionException(resolved.toString()); + } + return (Bean) resolved.iterator().next(); + } else { + // The set contains non-Arc beans - give our best effort + Set> resolved = new HashSet<>(beans); + for (Iterator> iterator = resolved.iterator(); iterator.hasNext();) { + if (!iterator.next().isAlternative()) { + iterator.remove(); + } + } + if (resolved.size() != 1) { + throw new AmbiguousResolutionException(resolved.toString()); + } + return resolved.iterator().next(); } } - if (resolvedBeans.size() > 1) { - // Try to resolve the ambiguity - for (Iterator> iterator = resolvedBeans.iterator(); iterator.hasNext();) { - InjectableBean bean = iterator.next(); - if (bean.getAlternativePriority() == null && (bean.getDeclaringBean() == null || bean.getDeclaringBean().getAlternativePriority() == null)) { + } + + private Set> resolve(Resolvable resolvable) { + return resolve(getMatchingBeans(resolvable)); + } + + private Set> resolve(List> matching) { + if (matching.isEmpty()) { + return Collections.emptySet(); + } else if (matching.size() == 1) { + return Collections.singleton(matching.get(0)); + } + // Try to resolve the ambiguity + List> resolved = new ArrayList<>(matching); + for (Iterator> iterator = resolved.iterator(); iterator.hasNext();) { + InjectableBean bean = iterator.next(); + if (bean.getAlternativePriority() == null && (bean.getDeclaringBean() == null || bean.getDeclaringBean().getAlternativePriority() == null)) { + // Remove non-alternatives + iterator.remove(); + } + } + if (resolved.size() == 1) { + return Collections.singleton(resolved.get(0)); + } else if (resolved.size() > 1) { + resolved.sort(this::compareAlternativeBeans); + // Keep only the highest priorities + Integer highest = getAlternativePriority(resolved.get(0)); + for (Iterator> iterator = resolved.iterator(); iterator.hasNext();) { + if (!highest.equals(getAlternativePriority(iterator.next()))) { iterator.remove(); } } - if (resolvedBeans.size() > 1) { - resolvedBeans.sort(this::compareAlternativeBeans); + if (resolved.size() == 1) { + return Collections.singleton(resolved.get(0)); + } + } + return new HashSet<>(matching); + } + + private Integer getAlternativePriority(InjectableBean bean) { + return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() : bean.getAlternativePriority(); + } + + List> getMatchingBeans(Resolvable resolvable) { + List> matching = new ArrayList<>(); + for (InjectableBean bean : beans) { + if (matches(bean, resolvable.requiredType, resolvable.qualifiers)) { + matching.add(bean); } } - return resolvedBeans; + return matching; } private int compareAlternativeBeans(InjectableBean bean1, InjectableBean bean2) { @@ -251,7 +320,14 @@ List> resolveObservers(Type eventType, S return resolvedObservers; } - List> geBeans(Type requiredType, Annotation... qualifiers) { + /** + * Performs typesafe resolution and resolves ambiguities. + * + * @param requiredType + * @param qualifiers + * @return the set of resolved beans + */ + Set> getResolvedBeans(Type requiredType, Annotation... qualifiers) { if (qualifiers == null || qualifiers.length == 0) { qualifiers = new Annotation[] { Default.Literal.INSTANCE }; } diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerImpl.java new file mode 100644 index 0000000000000..ac62309a2b36b --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerImpl.java @@ -0,0 +1,261 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.el.ELResolver; +import javax.el.ExpressionFactory; +import javax.enterprise.context.spi.Context; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.event.Event; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.AnnotatedField; +import javax.enterprise.inject.spi.AnnotatedMember; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedParameter; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanAttributes; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Decorator; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.InjectionTarget; +import javax.enterprise.inject.spi.InjectionTargetFactory; +import javax.enterprise.inject.spi.InterceptionFactory; +import javax.enterprise.inject.spi.InterceptionType; +import javax.enterprise.inject.spi.Interceptor; +import javax.enterprise.inject.spi.ObserverMethod; +import javax.enterprise.inject.spi.ProducerFactory; + +/** + * + * @author Martin Kouba + */ +public class BeanManagerImpl implements BeanManager { + + static final LazyValue INSTANCE = new LazyValue<>(BeanManagerImpl::new); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Object getReference(Bean bean, Type beanType, CreationalContext ctx) { + Objects.requireNonNull(bean); + Objects.requireNonNull(ctx); + if (bean instanceof InjectableBean && ctx instanceof CreationalContextImpl) { + return ArcContainerImpl.instance().beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx).get(); + } + throw new IllegalArgumentException( + "Arguments must be instances of " + InjectableBean.class + " and " + CreationalContextImpl.class + ": \nbean: " + bean + "\nctx: " + ctx); + } + + @Override + public Object getInjectableReference(InjectionPoint ij, CreationalContext ctx) { + throw new UnsupportedOperationException(); + } + + @Override + public CreationalContext createCreationalContext(Contextual contextual) { + return new CreationalContextImpl<>(); + } + + @Override + public Set> getBeans(Type beanType, Annotation... qualifiers) { + Objects.requireNonNull(beanType); + return ArcContainerImpl.instance().getBeans(beanType, qualifiers); + } + + @Override + public Set> getBeans(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public Bean getPassivationCapableBean(String id) { + throw new UnsupportedOperationException(); + } + + @Override + public Bean resolve(Set> beans) { + return ArcContainerImpl.instance().resolve(beans); + } + + @Override + public void validate(InjectionPoint injectionPoint) { + throw new UnsupportedOperationException(); + } + + @Override + public void fireEvent(Object event, Annotation... qualifiers) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> resolveObserverMethods(T event, Annotation... qualifiers) { + throw new UnsupportedOperationException(); + } + + @Override + public List> resolveDecorators(Set types, Annotation... qualifiers) { + throw new UnsupportedOperationException(); + } + + @Override + public List> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isScope(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNormalScope(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPassivatingScope(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isQualifier(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInterceptorBinding(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isStereotype(Class annotationType) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getInterceptorBindingDefinition(Class bindingType) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getStereotypeDefinition(Class stereotype) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean areQualifiersEquivalent(Annotation qualifier1, Annotation qualifier2) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean areInterceptorBindingsEquivalent(Annotation interceptorBinding1, Annotation interceptorBinding2) { + throw new UnsupportedOperationException(); + } + + @Override + public int getQualifierHashCode(Annotation qualifier) { + throw new UnsupportedOperationException(); + } + + @Override + public int getInterceptorBindingHashCode(Annotation interceptorBinding) { + throw new UnsupportedOperationException(); + } + + @Override + public Context getContext(Class scopeType) { + return Arc.container().getContext(scopeType); + } + + @Override + public ELResolver getELResolver() { + throw new UnsupportedOperationException(); + } + + @Override + public ExpressionFactory wrapExpressionFactory(ExpressionFactory expressionFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public AnnotatedType createAnnotatedType(Class type) { + throw new UnsupportedOperationException(); + } + + @Override + public InjectionTarget createInjectionTarget(AnnotatedType type) { + throw new UnsupportedOperationException(); + } + + @Override + public InjectionTargetFactory getInjectionTargetFactory(AnnotatedType annotatedType) { + throw new UnsupportedOperationException(); + } + + @Override + public ProducerFactory getProducerFactory(AnnotatedField field, Bean declaringBean) { + throw new UnsupportedOperationException(); + } + + @Override + public ProducerFactory getProducerFactory(AnnotatedMethod method, Bean declaringBean) { + throw new UnsupportedOperationException(); + } + + @Override + public BeanAttributes createBeanAttributes(AnnotatedType type) { + throw new UnsupportedOperationException(); + } + + @Override + public BeanAttributes createBeanAttributes(AnnotatedMember type) { + throw new UnsupportedOperationException(); + } + + @Override + public Bean createBean(BeanAttributes attributes, Class beanClass, InjectionTargetFactory injectionTargetFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public Bean createBean(BeanAttributes attributes, Class beanClass, ProducerFactory producerFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public InjectionPoint createInjectionPoint(AnnotatedField field) { + throw new UnsupportedOperationException(); + } + + @Override + public InjectionPoint createInjectionPoint(AnnotatedParameter parameter) { + throw new UnsupportedOperationException(); + } + + @Override + public T getExtension(Class extensionClass) { + throw new UnsupportedOperationException(); + } + + @Override + public InterceptionFactory createInterceptionFactory(CreationalContext ctx, Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public Event getEvent() { + throw new UnsupportedOperationException(); + } + + @Override + public Instance createInstance() { + throw new UnsupportedOperationException(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java index 23bd0d8ce67de..bc8619ab4acca 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java @@ -4,7 +4,7 @@ import javax.enterprise.inject.spi.BeanManager; /** - * Dummy {@link BeanManager} provider. + * {@link BeanManager} provider. * * @author Martin Kouba */ @@ -12,8 +12,7 @@ public class BeanManagerProvider implements InjectableReferenceProvider creationalContext) { - // TODO log a warning - return null; + return BeanManagerImpl.INSTANCE.get(); } } diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java index 04485f4d5750d..f5049c302af9f 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java @@ -79,4 +79,9 @@ public int getPriority() { return delegate.getPriority(); } + @Override + public Class getBeanClass() { + return delegate.getBeanClass(); + } + } diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java index feb2965a5597c..373ddeae57711 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java @@ -2,20 +2,22 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.Collections; import java.util.Set; import javax.enterprise.context.Dependent; -import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.InjectionPoint; /** - * Represents an injectable bean. It is an alternative to {@link javax.enterprise.inject.spi.Bean}. + * Represents an injectable bean. * * @author Martin Kouba * * @param */ -public interface InjectableBean extends Contextual, InjectableReferenceProvider { +public interface InjectableBean extends Bean, InjectableReferenceProvider { /** * @@ -27,7 +29,7 @@ default Class getScope() { /** * - * @return the set of bean type + * @return the set of bean types */ Set getTypes(); @@ -52,6 +54,31 @@ default InjectableBean getDeclaringBean() { return null; } + @Override + default String getName() { + return null; + } + + @Override + default Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + default Set getInjectionPoints() { + return Collections.emptySet(); + } + + @Override + default boolean isNullable() { + return false; + } + + @Override + default boolean isAlternative() { + return getAlternativePriority() != null; + } + /** * * @return the priority if the bean is an alternative, or {@code null} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java index c36d083db6e7f..551da92e91d3d 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java @@ -7,7 +7,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Set; import javax.enterprise.context.Dependent; @@ -48,13 +47,13 @@ public Iterator iterator() { @SuppressWarnings("unchecked") @Override public T get() { - List> beans = getBeans(); + Set> beans = getBeans(); if (beans.isEmpty()) { throw new UnsatisfiedResolutionException(); } else if (beans.size() > 1) { throw new AmbiguousResolutionException(); } - return getBeanInstance((InjectableBean) beans.get(0)); + return getBeanInstance((InjectableBean) beans.iterator().next()); } @Override @@ -107,8 +106,8 @@ private T getBeanInstance(InjectableBean bean) { return instance; } - private List> getBeans() { - return ArcContainerImpl.unwrap(Arc.container()).geBeans(type, qualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); + private Set> getBeans() { + return ArcContainerImpl.instance().getResolvedBeans(type, qualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); } class InstanceIterator implements Iterator { diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/beanmanager/BeanManagerTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/beanmanager/BeanManagerTest.java new file mode 100644 index 0000000000000..bb2fb3b9ccabd --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/beanmanager/BeanManagerTest.java @@ -0,0 +1,116 @@ +package org.jboss.protean.arc.test.beanmanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Priority; +import javax.enterprise.context.Dependent; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class BeanManagerTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Legacy.class, AlternativeLegacy.class, Fool.class); + + @Test + public void testGetBeans() { + BeanManager beanManager = Arc.container().instance(Legacy.class).get().getBeanManager(); + Set> beans = beanManager.getBeans(Legacy.class); + assertEquals(2, beans.size()); + assertEquals(AlternativeLegacy.class, beanManager.resolve(beans).getBeanClass()); + } + + @Test + public void testGetReference() { + Fool.DESTROYED.set(false); + Legacy.DESTROYED.set(false); + + BeanManager beanManager = Arc.container().instance(Legacy.class).get().getBeanManager(); + + Set> foolBeans = beanManager.getBeans(Fool.class); + assertEquals(1, foolBeans.size()); + @SuppressWarnings("unchecked") + Bean foolBean = (Bean) foolBeans.iterator().next(); + Fool fool1 = (Fool) beanManager.getReference(foolBean, Fool.class, beanManager.createCreationalContext(foolBean)); + Arc.container().withinRequest(() -> assertEquals(fool1.getId(), + ((Fool) beanManager.getReference(foolBean, Fool.class, beanManager.createCreationalContext(foolBean))).getId())).run(); + assertTrue(Fool.DESTROYED.get()); + + Set> legacyBeans = beanManager.getBeans(AlternativeLegacy.class); + assertEquals(1, legacyBeans.size()); + @SuppressWarnings("unchecked") + Bean legacyBean = (Bean) legacyBeans.iterator().next(); + CreationalContext ctx = beanManager.createCreationalContext(legacyBean); + Legacy legacy = (Legacy) beanManager.getReference(legacyBean, Legacy.class, ctx); + assertNotNull(legacy.getBeanManager()); + ctx.release(); + assertTrue(Legacy.DESTROYED.get()); + } + + @Dependent + static class Legacy { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(); + + @Inject + BeanManager beanManager; + + public BeanManager getBeanManager() { + return beanManager; + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + + } + + @Priority(1) + @Alternative + @Dependent + static class AlternativeLegacy extends Legacy { + + } + + @RequestScoped + static class Fool { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(); + + private String id; + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + + String getId() { + return id; + } + + } + +}