diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchive.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchive.java index 2e3e8ad797766..aeed65324a2e8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchive.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchive.java @@ -7,6 +7,7 @@ import org.jboss.jandex.IndexView; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.PathsCollection; /** @@ -63,10 +64,17 @@ public interface ApplicationArchive { PathsCollection getRootDirs(); /** - * Returns paths representing the application root paths. + * + * @return The paths representing the application root paths. */ PathsCollection getPaths(); + /** + * + * @return the artifact key or null if not available + */ + AppArtifactKey getArtifactKey(); + /** * Convenience method, returns the child path if it exists, otherwise null. * diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java index fdf614e4d33c1..b415317a34b9e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java @@ -5,6 +5,7 @@ import org.jboss.jandex.IndexView; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.builder.item.MultiBuildItem; @@ -14,20 +15,25 @@ public final class ApplicationArchiveImpl extends MultiBuildItem implements Appl private final PathsCollection rootDirs; private final boolean jar; private final PathsCollection paths; + private final AppArtifactKey artifactKey; public ApplicationArchiveImpl(IndexView indexView, Path archiveRoot, Closeable closeable, boolean jar, - Path archiveLocation) { - this.indexView = indexView; - this.rootDirs = PathsCollection.of(archiveRoot); - this.jar = jar; - this.paths = PathsCollection.of(archiveLocation); + Path archiveLocation, AppArtifactKey artifactKey) { + this(indexView, PathsCollection.of(archiveRoot), PathsCollection.of(archiveLocation), artifactKey); } - public ApplicationArchiveImpl(IndexView indexView, PathsCollection rootDirs, PathsCollection paths) { + public ApplicationArchiveImpl(IndexView indexView, PathsCollection rootDirs, PathsCollection paths, + AppArtifactKey artifactKey) { + this(indexView, rootDirs, paths, false, artifactKey); + } + + private ApplicationArchiveImpl(IndexView indexView, PathsCollection rootDirs, PathsCollection paths, boolean jar, + AppArtifactKey artifactKey) { this.indexView = indexView; this.rootDirs = rootDirs; this.paths = paths; - jar = false; + this.jar = jar; + this.artifactKey = artifactKey; } @Override @@ -62,4 +68,10 @@ public PathsCollection getRootDirs() { public PathsCollection getPaths() { return paths; } + + @Override + public AppArtifactKey getArtifactKey() { + return artifactKey; + } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java index afb65ce9d7753..c92197804a9e8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java @@ -104,7 +104,7 @@ ApplicationArchivesBuildItem build( markerFiles, root, additionalApplicationArchiveBuildItem, indexDependencyBuildItems, indexCache, curateOutcomeBuildItem); return new ApplicationArchivesBuildItem( - new ApplicationArchiveImpl(appindex.getIndex(), root.getRootDirs(), root.getPaths()), + new ApplicationArchiveImpl(appindex.getIndex(), root.getRootDirs(), root.getPaths(), null), applicationArchives); } @@ -131,7 +131,7 @@ private List scanForOtherIndexes(QuarkusBuildCloseablesBuild for (AdditionalApplicationArchiveBuildItem i : additionalApplicationArchives) { for (Path apPath : i.getPaths()) { if (!root.getPaths().contains(apPath) && indexedPaths.add(apPath)) { - appArchives.add(createApplicationArchive(buildCloseables, classLoader, indexCache, apPath)); + appArchives.add(createApplicationArchive(buildCloseables, classLoader, indexCache, apPath, null)); } } } @@ -164,7 +164,7 @@ public void addIndexDependencyPaths(List indexDependen } for (Path path : artifact.getPaths()) { if (!root.isExcludedFromIndexing(path) && !root.getPaths().contains(path) && indexedDeps.add(path)) { - appArchives.add(createApplicationArchive(buildCloseables, classLoader, indexCache, path)); + appArchives.add(createApplicationArchive(buildCloseables, classLoader, indexCache, path, key)); } } } @@ -175,10 +175,10 @@ public void addIndexDependencyPaths(List indexDependen private static ApplicationArchive createApplicationArchive(QuarkusBuildCloseablesBuildItem buildCloseables, ClassLoader classLoader, - IndexCache indexCache, Path dep) throws IOException { + IndexCache indexCache, Path dep, AppArtifactKey artifactKey) throws IOException { final FileSystem fs = Files.isDirectory(dep) ? null : buildCloseables.add(FileSystems.newFileSystem(dep, classLoader)); return new ApplicationArchiveImpl(indexPath(indexCache, dep), - fs == null ? dep : fs.getRootDirectories().iterator().next(), fs, fs != null, dep); + fs == null ? dep : fs.getRootDirectories().iterator().next(), fs, fs != null, dep, artifactKey); } private static IndexView indexPath(IndexCache indexCache, Path dep) throws IOException { @@ -232,7 +232,7 @@ private static void addMarkerFilePaths(Set applicationArchiveFiles, } appArchives .add(new ApplicationArchiveImpl(indexes.size() == 1 ? indexes.get(0) : CompositeIndex.create(indexes), - rootDirs.build(), artifactPaths)); + rootDirs.build(), artifactPaths, dep.getArtifact().getKey())); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java index 418dad5d7ba4b..6b00626499354 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java @@ -12,18 +12,18 @@ public class IndexDependencyConfig { * The maven groupId of the artifact to index */ @ConfigItem - String groupId; + public String groupId; /** * The maven artifactId of the artifact to index */ @ConfigItem - String artifactId; + public String artifactId; /** * The maven classifier of the artifact to index */ @ConfigItem - Optional classifier; + public Optional classifier; } diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 12b776eead53c..240ac0f87e9b5 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -76,10 +76,39 @@ quarkus.index-dependency..classifier=(this one is optional) For example, the following entries ensure that the `org.acme:acme-api` dependency is indexed: +=== How To Exclude Types and Dependencies from Discovery + +It may happen that some beans from third-party libraries do not work correctly in Quarkus. +A typical example is a bean injecting a portable extension. +In such case, it's possible to exclude types and dependencies from the bean discovery. +The `quarkus.arc.exclude-types` property accepts a list of string values that are used to match classes that should be excluded. + +.Value Examples +|=== +|Value|Description +|`org.acme.Foo`| Match the fully qualified name of the class +|`org.acme.*`| Match classes with package `org.acme` +|`org.acme.**`| Match classes where the package starts with `org.acme` +|`Bar`| Match the simple name of the class +|=== + +.Example application.properties +[source,properties] +---- +quarkus.arc.exclude-types=org.acme.Foo,org.acme.*,Bar <1><2><3> +---- +<1> Exclude the type `org.acme.Foo`. +<2> Exclude all types from the `org.acme` package. +<3> Exclude all types whose simple name is `Bar` + +It is also possible to exclude a dependency artifact that would be otherwise scanned for beans. +For example, because it contains a `beans.xml` descriptor. + +.Example application.properties [source,properties] ---- -quarkus.index-dependency.acme.group-id=org.acme <1> -quarkus.index-dependency.acme.artifact-id=acme-api <2> +quarkus.arc.exclude-dependency.acme.group-id=org.acme <1> +quarkus.arc.exclude-dependency.acme.artifact-id=acme-services <2> ---- <1> Value is a group id for a dependency identified by name `acme`. <2> Value is an artifact id for a dependency identified by name `acme`. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java index 7782a218047fb..913ad6dc7ea3a 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java @@ -6,10 +6,12 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import io.quarkus.arc.config.ConfigProperties; +import io.quarkus.deployment.index.IndexDependencyConfig; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -94,6 +96,29 @@ public class ArcConfig { @ConfigItem(defaultValue = "true") public boolean autoProducerMethods; + /** + * The list of types that should be excluded from discovery. + *

+ * An element value can be: + *

    + *
  • a fully qualified class name, i.e. {@code org.acme.Foo}
  • + *
  • a simple class name as defined by {@link Class#getSimpleName()}, i.e. {@code Foo}
  • + *
  • a package name with suffix {@code .*}, i.e. {@code org.acme.*}, matches a package
  • + *
  • a package name with suffix {@code .**}, i.e. {@code org.acme.**}, matches a package that starts with the value
  • + *
+ * If any element value matches a discovered type then the type is excluded from discovery, i.e. no beans and observer + * methods are created from this type. + */ + @ConfigItem + public Optional> excludeTypes; + + /** + * The artifacts that should be excluded from discovery. These artifacts would be otherwise scanned for beans, i.e. they + * contain a Jandex index or a beans.xml descriptor. + */ + @ConfigItem + Map excludeDependency; + public final boolean isRemoveUnusedBeansFieldValid() { return ALLOWED_REMOVE_UNUSED_BEANS_VALUES.contains(removeUnusedBeans.toLowerCase()); } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 5744309c93a99..5ffa0a8730aa8 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -269,7 +269,7 @@ public boolean test(BeanInfo bean) { builder.setAllowMocking(launchModeBuildItem.getLaunchMode() == LaunchMode.TEST); if (arcConfig.selectedAlternatives.isPresent()) { - final List> selectedAlternatives = initAlternativePredicates( + final List> selectedAlternatives = initClassPredicates( arcConfig.selectedAlternatives.get()); builder.setAlternativePriorities(new AlternativePriorities() { @@ -304,6 +304,13 @@ public Integer compute(AnnotationTarget target, Collection stere }); } + if (arcConfig.excludeTypes.isPresent()) { + for (Predicate predicate : initClassPredicates( + arcConfig.excludeTypes.get())) { + builder.addExcludeType(predicate); + } + } + BeanProcessor beanProcessor = builder.build(); ContextRegistrar.RegistrationContext context = beanProcessor.registerCustomContexts(); return new ContextRegistrationPhaseBuildItem(context, beanProcessor); @@ -467,7 +474,7 @@ CustomScopeAnnotationsBuildItem exposeCustomScopeNames(List> initAlternativePredicates(List selectedAlternatives) { + private List> initClassPredicates(List selectedAlternatives) { final String packMatch = ".*"; final String packStarts = ".**"; List> predicates = new ArrayList<>(); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java index 19cf34f02455a..9a1f71d0a84b7 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java @@ -16,17 +16,21 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; +import com.google.common.base.Objects; + import io.quarkus.arc.processor.BeanArchives; import io.quarkus.arc.processor.BeanDefiningAnnotation; import io.quarkus.arc.processor.BeanDeployment; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.runtime.LifecycleEventRunner; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; +import io.quarkus.deployment.index.IndexDependencyConfig; import io.quarkus.deployment.index.IndexingUtil; public class BeanArchiveProcessor { @@ -46,6 +50,8 @@ public class BeanArchiveProcessor { @Inject BuildProducer generatedClass; + ArcConfig config; + @BuildStep public BeanArchiveIndexBuildItem build(LiveReloadBuildItem liveReloadBuildItem) throws Exception { @@ -116,6 +122,9 @@ private IndexView buildApplicationIndex() { List indexes = new ArrayList<>(); for (ApplicationArchive archive : applicationArchivesBuildItem.getApplicationArchives()) { + if (isApplicationArchiveExcluded(archive)) { + continue; + } IndexView index = archive.getIndex(); // NOTE: Implicit bean archive without beans.xml contains one or more bean classes with a bean defining annotation and no extension if (archive.getChildPath("META-INF/beans.xml") != null || archive.getChildPath("WEB-INF/beans.xml") != null @@ -128,6 +137,20 @@ && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) { return CompositeIndex.create(indexes); } + private boolean isApplicationArchiveExcluded(ApplicationArchive archive) { + if (archive.getArtifactKey() != null) { + AppArtifactKey key = archive.getArtifactKey(); + for (IndexDependencyConfig excludeDependency : config.excludeDependency.values()) { + if (Objects.equal(key.getArtifactId(), excludeDependency.artifactId) + && Objects.equal(key.getGroupId(), excludeDependency.groupId) + && Objects.equal(key.getClassifier(), excludeDependency.classifier)) { + return true; + } + } + } + return false; + } + boolean containsBeanDefiningAnnotation(IndexView index, Collection beanDefiningAnnotations) { for (DotName beanDefiningAnnotation : beanDefiningAnnotations) { if (!index.getAnnotations(beanDefiningAnnotation).isEmpty()) { diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/Bravo.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/Bravo.java new file mode 100644 index 0000000000000..964fc97dd09d2 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/Bravo.java @@ -0,0 +1,14 @@ +package io.quarkus.arc.test.exclude; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.test.exclude.ExcludeTypesTest.Pong; + +@ApplicationScoped +class Bravo implements Pong { + + public String ping() { + return "bravo"; + } + +} \ No newline at end of file diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/ExcludeTypesTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/ExcludeTypesTest.java new file mode 100644 index 0000000000000..7679fdad1399c --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/ExcludeTypesTest.java @@ -0,0 +1,66 @@ +package io.quarkus.arc.test.exclude; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.exclude.bar.Bar; +import io.quarkus.arc.test.exclude.baz.bazzz.Baz; +import io.quarkus.test.QuarkusUnitTest; + +public class ExcludeTypesTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ExcludeTypesTest.class, Pong.class, Alpha.class, Bravo.class, Charlie.class, Bar.class, + Baz.class) + .addAsResource(new StringAsset( + "quarkus.arc.exclude-types=Alpha,io.quarkus.arc.test.exclude.Bravo,io.quarkus.arc.test.exclude.bar.*,,io.quarkus.arc.test.exclude.baz.**"), + "application.properties")); + + @Inject + Instance instance; + + @Test + public void testExcludeTypes() { + assertFalse(instance.select(Alpha.class).isResolvable()); + assertFalse(instance.select(Bravo.class).isResolvable()); + assertFalse(instance.select(Bar.class).isResolvable()); + assertEquals("charlie", instance.select(Pong.class).get().ping()); + } + + @ApplicationScoped + static class Alpha implements Pong { + + public String ping() { + return "alpga"; + } + + } + + @ApplicationScoped + static class Charlie implements Pong { + + public String ping() { + return "charlie"; + } + + } + + public interface Pong { + + String ping(); + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/bar/Bar.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/bar/Bar.java new file mode 100644 index 0000000000000..675518790cdb8 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/bar/Bar.java @@ -0,0 +1,15 @@ +package io.quarkus.arc.test.exclude.bar; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.test.exclude.ExcludeTypesTest.Pong; + +@ApplicationScoped +public class Bar implements Pong { + + @Override + public String ping() { + return "bar"; + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/baz/bazzz/Baz.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/baz/bazzz/Baz.java new file mode 100644 index 0000000000000..7aaf9eb3d40e2 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/exclude/baz/bazzz/Baz.java @@ -0,0 +1,15 @@ +package io.quarkus.arc.test.exclude.baz.bazzz; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.test.exclude.ExcludeTypesTest.Pong; + +@ApplicationScoped +public class Baz implements Pong { + + @Override + public String ping() { + return "baz"; + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index a3c2d98aaa195..6748230a67b42 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1,5 +1,6 @@ package io.quarkus.arc.processor; +import static io.quarkus.arc.processor.BeanProcessor.initAndSort; import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; @@ -90,51 +91,41 @@ public class BeanDeployment { private final List injectionPoints; private final boolean removeUnusedBeans; + private final List> unusedExclusions; + private final Set removedBeans; private final Map> customContexts; private final Collection beanDefiningAnnotations; + final boolean transformUnproxyableClasses; + private final boolean jtaCapabilities; private final AlternativePriorities alternativePriorities; - BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, - List annotationTransformers) { - this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - null, false, null, Collections.emptyMap(), Collections.emptyList(), false, false, null); - } + private final List> excludeTypes; - BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, - List annotationTransformers, - List injectionPointsTransformers, - List observerTransformers, - Collection resourceAnnotations, - BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions, - Map> additionalStereotypes, - List bindingRegistrars, - boolean transformUnproxyableClasses, - boolean jtaCapabilities, - AlternativePriorities alternativePriorities) { + BeanDeployment(IndexView index, BuildContextImpl buildContext, BeanProcessor.Builder builder) { this.buildContext = buildContext; Set beanDefiningAnnotations = new HashSet<>(); - if (additionalBeanDefiningAnnotations != null) { - beanDefiningAnnotations.addAll(additionalBeanDefiningAnnotations); + if (builder.additionalBeanDefiningAnnotations != null) { + beanDefiningAnnotations.addAll(builder.additionalBeanDefiningAnnotations); } this.beanDefiningAnnotations = beanDefiningAnnotations; - this.resourceAnnotations = new HashSet<>(resourceAnnotations); + this.resourceAnnotations = new HashSet<>(builder.resourceAnnotations); this.index = index; - this.annotationStore = new AnnotationStore(annotationTransformers, buildContext); + this.annotationStore = new AnnotationStore(initAndSort(builder.annotationTransformers, buildContext), buildContext); if (buildContext != null) { buildContext.putInternal(Key.ANNOTATION_STORE.asString(), annotationStore); } - this.injectionPointTransformer = new InjectionPointModifier(injectionPointsTransformers, buildContext); - this.observerTransformers = observerTransformers; - this.removeUnusedBeans = removeUnusedBeans; - this.unusedExclusions = removeUnusedBeans ? unusedExclusions : null; + this.injectionPointTransformer = new InjectionPointModifier( + initAndSort(builder.injectionPointTransformers, buildContext), buildContext); + this.observerTransformers = initAndSort(builder.observerTransformers, buildContext); + this.removeUnusedBeans = builder.removeUnusedBeans; + this.unusedExclusions = removeUnusedBeans ? new ArrayList<>(builder.removalExclusions) : null; this.removedBeans = new CopyOnWriteArraySet<>(); this.customContexts = new ConcurrentHashMap<>(); @@ -145,7 +136,7 @@ public class BeanDeployment { this.interceptorBindings = findInterceptorBindings(index); this.nonBindingFields = new HashMap<>(); - for (InterceptorBindingRegistrar registrar : bindingRegistrars) { + for (InterceptorBindingRegistrar registrar : builder.additionalInterceptorBindingRegistrars) { for (Map.Entry> bindingEntry : registrar.registerAdditionalBindings().entrySet()) { DotName dotName = bindingEntry.getKey(); ClassInfo classInfo = getClassByName(index, dotName); @@ -160,7 +151,7 @@ public class BeanDeployment { buildContextPut(Key.INTERCEPTOR_BINDINGS.asString(), Collections.unmodifiableMap(interceptorBindings)); this.stereotypes = findStereotypes(index, interceptorBindings, beanDefiningAnnotations, customContexts, - additionalStereotypes, annotationStore); + builder.additionalStereotypes, annotationStore); buildContextPut(Key.STEREOTYPES.asString(), Collections.unmodifiableMap(stereotypes)); this.transitiveInterceptorBindings = findTransitiveInterceptorBindigs(interceptorBindings.keySet(), index, @@ -173,9 +164,10 @@ public class BeanDeployment { this.beanResolver = new BeanResolver(this); this.interceptorResolver = new InterceptorResolver(this); - this.transformUnproxyableClasses = transformUnproxyableClasses; - this.jtaCapabilities = jtaCapabilities; - this.alternativePriorities = alternativePriorities; + this.transformUnproxyableClasses = builder.transformUnproxyableClasses; + this.jtaCapabilities = builder.jtaCapabilities; + this.alternativePriorities = builder.alternativePriorities; + this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList(); } ContextRegistrar.RegistrationContext registerCustomContexts(List contextRegistrars) { @@ -655,6 +647,10 @@ private List findBeans(Collection beanDefiningAnnotations, Li continue; } + if (isExcluded(beanClass)) { + continue; + } + if (!beanClass.hasNoArgsConstructor()) { int numberOfConstructorsWithoutInject = 0; int numberOfConstructorsWithInject = 0; @@ -866,6 +862,17 @@ private List findBeans(Collection beanDefiningAnnotations, Li return beans; } + private boolean isExcluded(ClassInfo beanClass) { + if (!excludeTypes.isEmpty()) { + for (Predicate exclude : excludeTypes) { + if (exclude.test(beanClass)) { + return true; + } + } + } + return false; + } + private void registerObserverMethods(Collection beanClasses, List observers, List injectionPoints, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index d8a8b47a9e011..e4e15cd244c12 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import javax.annotation.Priority; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.logging.Logger; @@ -70,51 +71,24 @@ public static Builder builder() { // Such as java.lang.Deprecated protected final Predicate injectionPointAnnotationsPredicate; - private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, - ResourceOutput output, - boolean sharedAnnotationLiterals, - ReflectionRegistration reflectionRegistration, - List annotationTransformers, - List injectionPointsTransformers, - List observerTransformers, - Collection resourceAnnotations, - List beanRegistrars, - List observerRegistrars, - List contextRegistrars, - List beanDeploymentValidators, - Predicate applicationClassPredicate, - boolean unusedBeansRemovalEnabled, - List> unusedExclusions, - Map> additionalStereotypes, - List interceptorBindingRegistrars, - boolean transformUnproxyableClasses, - boolean jtaCapabilities, - boolean generateSources, boolean allowMocking, - AlternativePriorities alternativePriorities) { - this.reflectionRegistration = reflectionRegistration; - this.applicationClassPredicate = applicationClassPredicate; - this.name = name; - this.output = output; - this.annotationLiterals = new AnnotationLiteralProcessor(sharedAnnotationLiterals, applicationClassPredicate); - this.generateSources = generateSources; - this.allowMocking = allowMocking; + private BeanProcessor(Builder builder) { + this.reflectionRegistration = builder.reflectionRegistration; + this.applicationClassPredicate = builder.applicationClassPredicate; + this.name = builder.name; + this.output = builder.output; + this.annotationLiterals = new AnnotationLiteralProcessor(builder.sharedAnnotationLiterals, applicationClassPredicate); + this.generateSources = builder.generateSources; + this.allowMocking = builder.allowMocking; // Initialize all build processors buildContext = new BuildContextImpl(); - buildContext.putInternal(Key.INDEX.asString(), index); - - this.beanRegistrars = initAndSort(beanRegistrars, buildContext); - this.observerRegistrars = initAndSort(observerRegistrars, buildContext); - this.contextRegistrars = initAndSort(contextRegistrars, buildContext); - this.beanDeploymentValidators = initAndSort(beanDeploymentValidators, buildContext); - this.beanDeployment = new BeanDeployment(index, additionalBeanDefiningAnnotations, - initAndSort(annotationTransformers, buildContext), - initAndSort(injectionPointsTransformers, buildContext), - initAndSort(observerTransformers, buildContext), - resourceAnnotations, buildContext, - unusedBeansRemovalEnabled, unusedExclusions, - additionalStereotypes, interceptorBindingRegistrars, - transformUnproxyableClasses, jtaCapabilities, alternativePriorities); + buildContext.putInternal(Key.INDEX.asString(), builder.index); + + this.beanRegistrars = initAndSort(builder.beanRegistrars, buildContext); + this.observerRegistrars = initAndSort(builder.observerRegistrars, buildContext); + this.contextRegistrars = initAndSort(builder.contextRegistrars, buildContext); + this.beanDeploymentValidators = initAndSort(builder.beanDeploymentValidators, buildContext); + this.beanDeployment = new BeanDeployment(builder.index, buildContext, builder); // Make it configurable if we find that the set of annotations needs to grow this.injectionPointAnnotationsPredicate = annotationName -> !annotationName.equals(DotNames.DEPRECATED); @@ -266,39 +240,38 @@ public void accept(BytecodeTransformer transformer) { public static class Builder { - private String name = DEFAULT_NAME; - - private IndexView index; - - private Collection additionalBeanDefiningAnnotations = Collections.emptySet(); - private Map> additionalStereotypes = Collections.emptyMap(); + String name = DEFAULT_NAME; - private ResourceOutput output; + IndexView index; - private boolean sharedAnnotationLiterals = true; + Collection additionalBeanDefiningAnnotations = Collections.emptySet(); + Map> additionalStereotypes = Collections.emptyMap(); - private ReflectionRegistration reflectionRegistration = ReflectionRegistration.NOOP; + ResourceOutput output; - private final List resourceAnnotations = new ArrayList<>(); + boolean sharedAnnotationLiterals = true; + ReflectionRegistration reflectionRegistration = ReflectionRegistration.NOOP; - private final List annotationTransformers = new ArrayList<>(); - private final List injectionPointTransformers = new ArrayList<>(); - private final List observerTransformers = new ArrayList<>(); - private final List beanRegistrars = new ArrayList<>(); - private final List observerRegistrars = new ArrayList<>(); - private final List contextRegistrars = new ArrayList<>(); - private final List additionalInterceptorBindingRegistrars = new ArrayList<>(); - private final List beanDeploymentValidators = new ArrayList<>(); + final List resourceAnnotations = new ArrayList<>(); + final List annotationTransformers = new ArrayList<>(); + final List injectionPointTransformers = new ArrayList<>(); + final List observerTransformers = new ArrayList<>(); + final List beanRegistrars = new ArrayList<>(); + final List observerRegistrars = new ArrayList<>(); + final List contextRegistrars = new ArrayList<>(); + final List additionalInterceptorBindingRegistrars = new ArrayList<>(); + final List beanDeploymentValidators = new ArrayList<>(); - private boolean removeUnusedBeans = false; - private final List> removalExclusions = new ArrayList<>(); + boolean removeUnusedBeans = false; + final List> removalExclusions = new ArrayList<>(); - private boolean generateSources = false; - private boolean jtaCapabilities = false; - private boolean transformUnproxyableClasses = false; - private boolean allowMocking = false; + boolean generateSources = false; + boolean jtaCapabilities = false; + boolean transformUnproxyableClasses = false; + boolean allowMocking = false; - private AlternativePriorities alternativePriorities; + AlternativePriorities alternativePriorities; + List> excludeTypes = new ArrayList<>(); private Predicate applicationClassPredicate = new Predicate() { @Override @@ -473,18 +446,24 @@ public Builder setAlternativePriorities(AlternativePriorities priorities) { return this; } + /** + * Specify the types that should be excluded from discovery. + * + * @param predicate + * @return self + */ + public Builder addExcludeType(Predicate predicate) { + this.excludeTypes.add(predicate); + return this; + } + public BeanProcessor build() { - return new BeanProcessor(name, index, additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals, - reflectionRegistration, annotationTransformers, injectionPointTransformers, observerTransformers, - resourceAnnotations, beanRegistrars, observerRegistrars, contextRegistrars, beanDeploymentValidators, - applicationClassPredicate, removeUnusedBeans, removalExclusions, additionalStereotypes, - additionalInterceptorBindingRegistrars, transformUnproxyableClasses, jtaCapabilities, generateSources, - allowMocking, alternativePriorities); + return new BeanProcessor(this); } } - private static List initAndSort(List extensions, BuildContext buildContext) { + static List initAndSort(List extensions, BuildContext buildContext) { for (Iterator iterator = extensions.iterator(); iterator.hasNext();) { if (!iterator.next().initialize(buildContext)) { iterator.remove(); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java index d0fc79c4a8703..12bddc9e90371 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java @@ -40,7 +40,7 @@ public void testInjections() throws IOException { Type listStringType = ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null); - BeanDeployment deployment = new BeanDeployment(index, null, null); + BeanDeployment deployment = BeanProcessor.builder().setIndex(index).build().getBeanDeployment(); deployment.registerCustomContexts(Collections.emptyList()); deployment.registerBeans(Collections.emptyList()); BeanInfo barBean = deployment.getBeans().stream().filter(b -> b.getTarget().get().equals(barClass)).findFirst().get(); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java index 0a71bc8eb606e..c000f4b729fe9 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java @@ -12,7 +12,6 @@ import java.util.AbstractCollection; import java.util.AbstractList; import java.util.Collection; -import java.util.Collections; import java.util.List; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; @@ -35,7 +34,8 @@ public void testQualifiers() throws IOException { DotName fooQualifierName = name(FooQualifier.class); ClassInfo fooClass = index.getClassByName(fooName); - BeanInfo bean = Beans.createClassBean(fooClass, new BeanDeployment(index, null, Collections.emptyList()), null); + BeanInfo bean = Beans.createClassBean(fooClass, BeanProcessor.builder().setIndex(index).build().getBeanDeployment(), + null); AnnotationInstance requiredFooQualifier = index.getAnnotations(fooQualifierName).stream() .filter(a -> Kind.FIELD.equals(a.target().kind()) && a.target().asField().name().equals("foo")).findFirst() diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java index 6ec22688186d6..c5278cbec82a0 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java @@ -35,7 +35,7 @@ public void testResolver() throws IOException { Collection.class, List.class, Iterable.class, Object.class, String.class); - BeanDeployment deployment = new BeanDeployment(index, null, null); + BeanDeployment deployment = BeanProcessor.builder().setIndex(index).build().getBeanDeployment(); DotName fooName = name(Foo.class); ClassInfo fooClass = index.getClassByName(fooName); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java deleted file mode 100644 index 5eb7fee795f30..0000000000000 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.quarkus.arc.processor; - -import java.util.function.Predicate; -import org.jboss.jandex.DotName; - -public class TruePredicate implements Predicate { - - public static TruePredicate INSTANCE = new TruePredicate(); - - @Override - public boolean test(DotName dotName) { - return true; - } -} diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index f36931cd5a1fb..320d82da4fbd2 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -32,7 +32,7 @@ public void testGetTypeClosure() throws IOException { DotName producerName = DotName.createSimple(Producer.class.getName()); ClassInfo fooClass = index.getClassByName(fooName); Map> resolvedTypeVariables = new HashMap<>(); - BeanDeployment dummyDeployment = dummyDeployment(index); + BeanDeployment dummyDeployment = BeanProcessor.builder().setIndex(index).build().getBeanDeployment(); // Baz, Foo, Object Set bazTypes = Types.getTypeClosure(index.getClassByName(bazName), null, @@ -76,13 +76,6 @@ public void testGetTypeClosure() throws IOException { assertEquals(1, producerFieldTypes.size()); } - BeanDeployment dummyDeployment(IndexView index) { - return new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false, null); - } - static class Foo { T field; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java index 9c8ee1b767581..e118aa52553e5 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.io.FileUtils; import org.apache.maven.shared.invoker.MavenInvocationException; @@ -184,6 +185,44 @@ public void testThatMutableFastJarWorks() throws MavenInvocationException, IOExc } } + /** + * Tests that quarkus.arc.exclude-dependency.* can be used for modules in a multimodule project + */ + @Test + public void testArcExcludeDependencyOnLocalModule() throws Exception { + File testDir = initProject("projects/arc-exclude-dependencies"); + RunningInvoker running = new RunningInvoker(testDir, false); + + MavenProcessInvocationResult result = running.execute(Arrays.asList("package", "-DskipTests"), Collections.emptyMap()); + await().atMost(1, TimeUnit.MINUTES).until(() -> result.getProcess() != null && !result.getProcess().isAlive()); + assertThat(running.log()).containsIgnoringCase("BUILD SUCCESS"); + running.stop(); + + File targetDir = new File(testDir.getAbsoluteFile(), "runner" + File.separator + "target"); + Path jar = targetDir.toPath().toAbsolutePath() + .resolve(Paths.get("acme-1.0-SNAPSHOT-runner.jar")); + File output = new File(targetDir, "output.log"); + output.createNewFile(); + + Process process = doLaunch(jar, output); + try { + // Wait until server up + AtomicReference response = new AtomicReference<>(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/hello", true); + response.set(ret); + return ret.contains("hello:"); + }); + + // Test that bean is not resolvable + assertThat(response.get()).containsIgnoringCase("hello:false"); + } finally { + process.destroy(); + } + } + private Process doLaunch(Path jar, File output) throws IOException { List commands = new ArrayList<>(); commands.add(JavaBinFinder.findBin()); diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/pom.xml b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/pom.xml new file mode 100644 index 0000000000000..4802625d1a52f --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../ + + org.acme + quarkus-quickstart-multimodule-lib + 1.0-SNAPSHOT + + + + + io.quarkus + quarkus-resteasy + + + diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/src/main/java/org/acme/SomeBean.java b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/src/main/java/org/acme/SomeBean.java new file mode 100644 index 0000000000000..8d0fe720363d5 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/library/src/main/java/org/acme/SomeBean.java @@ -0,0 +1,8 @@ +package org.acme; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class SomeBean { + +} diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/pom.xml b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/pom.xml new file mode 100644 index 0000000000000..9abeb997e3eb0 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + pom + + + io.quarkus + quarkus-bom + 999-SNAPSHOT + 999-SNAPSHOT + 2.22.1 + UTF-8 + 1.8 + 1.8 + + + library + runner + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + org.acme + quarkus-quickstart-multimodule-lib + 1.0-SNAPSHOT + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/pom.xml b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/pom.xml new file mode 100644 index 0000000000000..244bf157e7467 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../ + + org.acme + acme + 1.0-SNAPSHOT + + + + + io.quarkus + quarkus-resteasy + + + org.acme + quarkus-quickstart-multimodule-lib + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + + + + + + native + + + native + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..ba370801fa508 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,24 @@ +package org.acme; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class HelloResource { + + @Inject + Instance bean; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello:" + bean.isResolvable(); + } + +} diff --git a/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/resources/application.properties b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/resources/application.properties new file mode 100644 index 0000000000000..6a8b2671dcf24 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/arc-exclude-dependencies/runner/src/main/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.arc.exclude-dependency.model.group-id=org.acme +quarkus.arc.exclude-dependency.model.artifact-id=quarkus-quickstart-multimodule-lib \ No newline at end of file