Skip to content

Commit

Permalink
Merge pull request #10007 from manovotn/issue9941
Browse files Browse the repository at this point in the history
Make it possible to mark beans as unremovable via MP config.
  • Loading branch information
manovotn authored Jun 16, 2020
2 parents 6308736 + 538f0a9 commit eea5103
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 2 deletions.
18 changes: 18 additions & 0 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,24 @@ This optimization applies to all forms of bean declarations: bean class, produce
Users can instruct the container to not remove any of their specific beans (even if they satisfy all the rules specified above) by annotating them with `io.quarkus.arc.Unremovable`.
This annotation can be placed on the types, producer methods, and producer fields.

Since this is not always possible, there is an option to achieve the same via `application.properties`.
The `quarkus.arc.unremovable-types` property accepts a list of string values that are used to match beans based on their name or package.

.Value Examples
|===
|Value|Description
|`org.acme.Foo`| Match the fully qualified name of the bean class
|`org.acme.*`| Match beans where the package of the bean class is `org.acme`
|`org.acme.**`| Match beans where the package of the bean class starts with `org.acme`
|`Bar`| Match the simple name of the bean class
|===

.Example application.properties
[source,properties]
----
quarkus.arc.unremovable-types=org.acme.Foo,org.acme.*,Bar
----

Furthermore, extensions can eliminate possible false positives by producing `UnremovableBeanBuildItem`.

Finally, Quarkus provides a middle ground for the bean removal optimization where application beans are never removed whether or not they are unused,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ public class ArcConfig {
@ConfigItem
public Optional<List<String>> excludeTypes;

/**
* List of types that should be considered unremovable regardless of whether they are directly used or not.
* This is a configuration option equivalent to using {@link io.quarkus.arc.Unremovable} annotation.
*
* <p>
* An element value can be:
* <ul>
* <li>a fully qualified class name, i.e. {@code org.acme.Foo}</li>
* <li>a simple class name as defined by {@link Class#getSimpleName()}, i.e. {@code Foo}</li>
* <li>a package name with suffix {@code .*}, i.e. {@code org.acme.*}, matches a package</li>
* <li>a package name with suffix {@code .**}, i.e. {@code org.acme.**}, matches a package that starts with the value</li>
* </ul>
* If any element value matches a discovered bean, then such a bean is considered unremovable.
*
* @see {@link #removeUnusedBeans}
* @see {@link io.quarkus.arc.Unremovable}
*/
@ConfigItem
public Optional<List<String>> unremovableTypes;

/**
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,25 @@ protected DotName getDotName(BeanInfo bean) {
for (UnremovableBeanBuildItem exclusion : removalExclusions) {
builder.addRemovalExclusion(exclusion.getPredicate());
}
// unremovable beans specified in application.properties
if (arcConfig.unremovableTypes.isPresent()) {
List<Predicate<ClassInfo>> classPredicates = initClassPredicates(arcConfig.unremovableTypes.get());
builder.addRemovalExclusion(new Predicate<BeanInfo>() {
@Override
public boolean test(BeanInfo beanInfo) {
ClassInfo beanClass = beanInfo.getImplClazz();
if (beanClass != null) {
// if any of the predicates match, we make the given bean unremovable
for (Predicate<ClassInfo> predicate : classPredicates) {
if (predicate.test(beanClass)) {
return true;
}
}
}
return false;
}
});
}
if (testClassPredicate.isPresent()) {
builder.addRemovalExclusion(new Predicate<BeanInfo>() {
@Override
Expand Down Expand Up @@ -478,11 +497,11 @@ CustomScopeAnnotationsBuildItem exposeCustomScopeNames(List<ContextRegistrarBuil
return new CustomScopeAnnotationsBuildItem(names);
}

private List<Predicate<ClassInfo>> initClassPredicates(List<String> selectedAlternatives) {
private List<Predicate<ClassInfo>> initClassPredicates(List<String> types) {
final String packMatch = ".*";
final String packStarts = ".**";
List<Predicate<ClassInfo>> predicates = new ArrayList<>();
for (String val : selectedAlternatives) {
for (String val : types) {
if (val.endsWith(packMatch)) {
// Package matches
final String pack = val.substring(0, val.length() - packMatch.length());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.quarkus.arc.test.unused;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class Charlie {

public String ping() {
return "ok";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.arc.test.unused;

/**
* Not a bean on its own but has a producer
*/
public class Delta {

private String s;

public Delta(String s) {
this.s = s;
}

public String ping() {
return s;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.arc.test.unused;

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

import io.quarkus.arc.Unremovable;

@ApplicationScoped
@Unremovable
public class ProducerBean {

@Produces
@ApplicationScoped
Delta produceDelta() {
return new Delta("ok");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.arc.test.unused;

import javax.enterprise.context.ApplicationScoped;

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.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.test.unused.subpackage.Beta;
import io.quarkus.test.QuarkusUnitTest;

public class UnusedExclusionTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(UnusedExclusionTest.class, Alpha.class, Beta.class, Charlie.class, Delta.class,
ProducerBean.class)
.addAsResource(new StringAsset(
"quarkus.arc.unremovable-types=io.quarkus.arc.test.unused.UnusedExclusionTest$Alpha,io.quarkus.arc.test.unused.subpackage.**,io.quarkus.arc.test.unused.Charlie,Delta"),
"application.properties"));

@Test
public void testBeansWereNotRemoved() {
ArcContainer container = Arc.container();
String expectedBeanResponse = "ok";
InstanceHandle<Alpha> alphaInstance = container.instance(Alpha.class);
Assertions.assertTrue(alphaInstance.isAvailable());
Assertions.assertEquals(expectedBeanResponse, alphaInstance.get().ping());

InstanceHandle<Beta> betaInstance = container.instance(Beta.class);
Assertions.assertTrue(betaInstance.isAvailable());
Assertions.assertEquals(expectedBeanResponse, betaInstance.get().ping());

InstanceHandle<Charlie> charlieInstance = container.instance(Charlie.class);
Assertions.assertTrue(charlieInstance.isAvailable());
Assertions.assertEquals(expectedBeanResponse, charlieInstance.get().ping());

InstanceHandle<Delta> deltaInstance = container.instance(Delta.class);
Assertions.assertTrue(deltaInstance.isAvailable());
Assertions.assertEquals(expectedBeanResponse, deltaInstance.get().ping());
}

// unused bean, won't be removed
@ApplicationScoped
static class Alpha {

public String ping() {
return "ok";
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.arc.test.unused.subpackage;

import javax.enterprise.context.ApplicationScoped;

/**
* Another unused bean that shouldn't be removed.
*/
@ApplicationScoped
public class Beta {

public String ping() {
return "ok";
}
}

0 comments on commit eea5103

Please sign in to comment.