Skip to content

Commit

Permalink
Fix class scanning forcing classes as beans
Browse files Browse the repository at this point in the history
Resolves #77.
  • Loading branch information
kdubb authored and manovotn committed Apr 15, 2019
1 parent 6098043 commit e916693
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 29 deletions.
61 changes: 32 additions & 29 deletions junit5/src/main/java/org/jboss/weld/junit5/auto/ClassScanning.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,18 @@
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.support.HierarchyTraversalMode;
import org.junit.platform.commons.util.AnnotationUtils;
import org.junit.platform.commons.util.CollectionUtils;
import org.junit.platform.commons.util.Preconditions;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ConversationScoped;
import javax.decorator.Decorator;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.NormalScope;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.Stereotype;
import javax.enterprise.inject.spi.Extension;
import javax.inject.Inject;
import javax.inject.Qualifier;
import javax.interceptor.Interceptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
Expand Down Expand Up @@ -107,39 +104,44 @@ static void scanForRequiredBeanClass(Class<?> testClass, Weld weld, boolean expl

findFirstAnnotatedConstructor(currClass, Inject.class)
.map(Stream::of).orElseGet(Stream::empty)
.flatMap(cons -> getExecutableParameterTypes(cons, weld, explicitInjection).stream())
.flatMap(cons -> getExecutableParameterTypes(cons, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

findAnnotatedFields(currClass, Produces.class).stream()
.map(Field::getType)
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, Produces.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.map(Method::getReturnType)
.flatMap(method ->
Stream.concat(
getExecutableParameterTypes(method, explicitInjection).stream(),
Stream.of(method.getReturnType())
)
)
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, Test.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, RepeatedTest.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, BeforeAll.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, BeforeEach.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findAnnotatedMethods(currClass, AfterAll.class, HierarchyTraversalMode.BOTTOM_UP).stream()
.flatMap(method -> getExecutableParameterTypes(method, weld, explicitInjection).stream())
.flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
.forEach(cls -> addClassesToProcess(classesToProcess, cls));

AnnotationSupport.findRepeatableAnnotations(currClass, AddPackages.class)
Expand All @@ -151,7 +153,10 @@ static void scanForRequiredBeanClass(Class<?> testClass, Weld weld, boolean expl
AnnotationSupport.findRepeatableAnnotations(currClass, AddBeanClasses.class).stream()
.flatMap(ann -> stream(ann.value()))
.distinct()
.forEach(weld::addBeanClass);
.forEach(it -> {
classesToProcess.add(it);
weld.addBeanClass(it);
});

AnnotationSupport.findRepeatableAnnotations(currClass, AddExtensions.class).stream()
.flatMap(ann -> stream(ann.value()))
Expand All @@ -163,6 +168,7 @@ static void scanForRequiredBeanClass(Class<?> testClass, Weld weld, boolean expl
.flatMap(ann -> stream(ann.value()))
.distinct()
.forEach(interceptor -> {
classesToProcess.add(interceptor);
weld.addInterceptor(interceptor);
weld.addBeanClass(interceptor);
});
Expand All @@ -171,6 +177,7 @@ static void scanForRequiredBeanClass(Class<?> testClass, Weld weld, boolean expl
.flatMap(ann -> stream(ann.value()))
.distinct()
.forEach(decorator -> {
classesToProcess.add(decorator);
weld.addDecorator(decorator);
weld.addBeanClass(decorator);
});
Expand Down Expand Up @@ -224,24 +231,20 @@ private static void addClassesToProcess(Collection<Class<?>> classesToProcess, T

}

private static List<Class<?>> getExecutableParameterTypes(Executable executable, Weld weld, boolean explicitInjection) {
private static List<Class<?>> getExecutableParameterTypes(Executable executable, boolean explicitInjection) {

List<Class<?>> types = new ArrayList<>();

if (explicitInjection) {
Annotation[][] paramAnns = executable.getParameterAnnotations();
Class<?>[] paramTypes = executable.getParameterTypes();
for (int c = 0; c < paramAnns.length; ++c) {
if (stream(paramAnns[c]).anyMatch(ann -> isAnnotated(ann.annotationType(), Qualifier.class) || isAnnotated(ann.annotationType(), NormalScope.class))) {
weld.addBeanClass(paramTypes[c]);
for (int c = 0; c < paramTypes.length; ++c) {
if (stream(paramAnns[c]).anyMatch(ClassScanning::isBeanParameterAnnotation)) {
types.add(paramTypes[c]);
}
}
} else {
for (Class<?> paramType : executable.getParameterTypes()) {
weld.addBeanClass(paramType);
types.add(paramType);
}
types.addAll(asList(executable.getParameterTypes()));
}

return types;
Expand All @@ -255,14 +258,14 @@ private static Extension createExtension(Class<? extends Extension> clazz) {
}
}

private static boolean isBeanParameterAnnotation(Annotation ann) {
return isAnnotated(ann.annotationType(), Qualifier.class);
}

private static boolean hasBeanDefiningAnnotation(Class<?> clazz) {
return
AnnotationUtils.isAnnotated(clazz, ApplicationScoped.class) ||
AnnotationUtils.isAnnotated(clazz, SessionScoped.class) ||
AnnotationUtils.isAnnotated(clazz, ConversationScoped.class) ||
AnnotationUtils.isAnnotated(clazz, RequestScoped.class) ||
AnnotationUtils.isAnnotated(clazz, Dependent.class) ||
AnnotationUtils.isAnnotated(clazz, Stereotype.class);
return isAnnotated(clazz, NormalScope.class) || isAnnotated(clazz, Dependent.class) ||
isAnnotated(clazz, Interceptor.class) || isAnnotated(clazz, Decorator.class) ||
isAnnotated(clazz, Stereotype.class);
}

private static List<Field> findAllFieldsInHierarchy(Class<?> clazz) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.jboss.weld.junit5.auto;

import org.jboss.weld.junit5.auto.beans.Engine;
import org.jboss.weld.junit5.auto.beans.V6;
import org.junit.jupiter.api.Test;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.inject.Named;

import static org.junit.jupiter.api.Assertions.assertNotNull;

@EnableAutoWeld
public class ProducerMethodParametersScanningTest {

@Produces
@Named("custom")
@Dependent
Engine getEngine(V6 v6) {
return new Engine() {
@Override
public int getThrottle() {
return v6.getThrottle();
}

@Override
public void setThrottle(int value) {
v6.setThrottle(value);
}
};
}

@Test
public void test(@Named("custom") Engine engine) {
assertNotNull(engine);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.jboss.weld.junit5.auto;


import org.jboss.weld.junit5.auto.beans.InjectedV8;
import org.jboss.weld.junit5.auto.beans.V8;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import javax.enterprise.inject.Produces;

import static org.junit.jupiter.api.Assertions.assertNotNull;


@EnableAutoWeld
@AddBeanClasses(InjectedV8.class)
public class ScannedClassesAreNotForcedBeansTest {

/**
* V8 is *not* a "bean" class, in that it has no bean defining annotation. To satisfy
* a dependency on it, a producer method or a reference in an @AddBeanClasses annotation
* is required.
* <p>
* This test ensures that as V8 is discovered via class scanning it is not automatically
* added as a bean class. If it was added that way, the bean class and producer method would
* create an ambiguous injection case for V8.
*
* NOTE: This case only tests for classes found as non-parameters (e.g. injected fields)
*/

@Produces
private V8 engine = new V8();

@Test
@DisplayName("Test that V8 is not ambiguous to do incorrectly being identified as a bean class")
void test(V8 engine) {
assertNotNull(engine);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.jboss.weld.junit5.auto;


import org.jboss.weld.junit5.auto.beans.ConstructedV8;
import org.jboss.weld.junit5.auto.beans.V8;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import javax.enterprise.inject.Produces;

import static org.junit.jupiter.api.Assertions.assertNotNull;


@EnableAutoWeld
@AddBeanClasses(ConstructedV8.class)
public class ScannedParameterClassesAreNotForcedBeansTest {

/**
* V8 is *not* a "bean" class, in that it has no bean defining annotation. To satisfy
* a dependency on it, a producer method or a reference in an @AddBeanClasses annotation
* is required.
* <p>
* This test ensures that as V8 is discovered via class scanning it is not automatically
* added as a bean class. If it was added that way, the bean class and producer method would
* create an ambiguous injection case for V8.
*
* NOTE: This case only tests for classes found from parameters (e.g. constructor injection
* parameters)
*/

@Produces
private V8 engine = new V8();

@Test
@DisplayName("Test that V8 is not ambiguous to do incorrectly being identified as a bean class from parameter")
void test(V8 engine) {
assertNotNull(engine);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jboss.weld.junit5.auto.beans;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class ConstructedV8 {

private V8 engine;

@Inject
public ConstructedV8(V8 engine) {
this.engine = engine;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jboss.weld.junit5.auto.beans;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class InjectedV8 {

@Inject
private V8 engine;

}

0 comments on commit e916693

Please sign in to comment.