Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to disable ConfigProperties #17063

Merged
merged 1 commit into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/src/main/asciidoc/config-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,37 @@ complex:

WARNING: A limitation of such configuration is that the types used as the generic types of the lists need to be classes and not interfaces.

=== Combining ConfigProperties with build time conditions

Quarkus allows you to define conditions evaluated at build time (`@IfBuildProfile`, `@UnlessBuildProfile`, `@IfBuildProperty` and `@UnlessBuildProperty`) to enable or not the annotations `@ConfigProperties` and `@ConfigPrefix` which gives you a very flexible way to map your configuration.

Let's assume that the configuration of a service is mapped thanks to a `@ConfigProperties` and you don't need this part of the configuration for your tests as it will be mocked, in that case you can define a build time condition like in the next example:

`ServiceConfiguration.java`
[source,java]
----
@UnlessBuildProfile("test") <1>
@ConfigProperties
public class ServiceConfiguration {
public String user;
public String password;
}
----
<1> The annotation `@ConfigProperties` is considered if and only if the active profile is not `test`.

`SomeBean.java`
[source,java]
----
@ApplicationScoped
public class SomeBean {

@Inject
Instance<ServiceConfiguration> serviceConfiguration; <1>

}
----
<1> As the configuration of the service could be missing, we need to use `Instance<ServiceConfiguration>` as type at the injection point.

[[configuration_profiles]]
== Configuration Profiles

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.quarkus.arc.deployment;

import java.util.Set;

import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.MethodInfo;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* A type of build item that contains only declaring classes, methods and fields that have been annotated with
* unsuccessful build time conditions. It aims to be used to manage the exclusion of the annotations thanks to the
* build time conditions also known as {@code IfBuildProfile}, {@code UnlessBuildProfile}, {@code IfBuildProperty} and
* {@code UnlessBuildProperty}
*
* @see io.quarkus.arc.deployment.PreAdditionalBeanBuildTimeConditionBuildItem
* @see io.quarkus.arc.profile.IfBuildProfile
* @see io.quarkus.arc.profile.UnlessBuildProfile
* @see io.quarkus.arc.properties.IfBuildProperty
* @see io.quarkus.arc.properties.UnlessBuildProperty
*/
public final class BuildExclusionsBuildItem extends SimpleBuildItem {

private final Set<String> excludedDeclaringClasses;
private final Set<String> excludedMethods;
private final Set<String> excludedFields;

public BuildExclusionsBuildItem(Set<String> excludedDeclaringClasses,
Set<String> excludedMethods,
Set<String> excludedFields) {
this.excludedDeclaringClasses = excludedDeclaringClasses;
this.excludedMethods = excludedMethods;
this.excludedFields = excludedFields;
}

public Set<String> getExcludedDeclaringClasses() {
return excludedDeclaringClasses;
}

public Set<String> getExcludedMethods() {
return excludedMethods;
}

public Set<String> getExcludedFields() {
return excludedFields;
}

/**
* Indicates whether the given target is excluded following the next rules:
* <p>
* <ul>
* <li>In case of a class it will check if it is part of the excluded classes</li>
* <li>In case of a method it will check if it is part of the excluded methods and if its declaring class
* is excluded</li>
* <li>In case of a method parameter it will check if its corresponding method is part of the excluded methods
* and if its declaring class is excluded</li>
* <li>In case of a field it will check if it is part of the excluded field and if its declaring class is excluded</li>
* <li>In all other cases, it is not excluded</li>
* </ul>
*
* @param target the target to check.
* @return {@code true} if the target is excluded, {@code false} otherwise.
*/
public boolean isExcluded(AnnotationTarget target) {
switch (target.kind()) {
case CLASS:
return excludedDeclaringClasses.contains(targetMapper(target));
case METHOD:
return excludedMethods.contains(targetMapper(target)) ||
excludedDeclaringClasses.contains(targetMapper(target.asMethod().declaringClass()));
case METHOD_PARAMETER:
final MethodInfo method = target.asMethodParameter().method();
return excludedMethods.contains(targetMapper(method)) ||
excludedDeclaringClasses.contains(targetMapper(method.declaringClass()));
case FIELD:
return excludedFields.contains(targetMapper(target)) ||
excludedDeclaringClasses.contains(targetMapper(target.asField().declaringClass()));
default:
return false;
}
}

/**
* Converts the given target into a String unique representation.
*
* @param target the target to convert.
* @return a unique representation as a {@code String} of the target
*/
public static String targetMapper(AnnotationTarget target) {
final AnnotationTarget.Kind kind = target.kind();
if (kind == AnnotationTarget.Kind.CLASS) {
return target.asClass().toString();
} else if (kind == AnnotationTarget.Kind.METHOD) {
final MethodInfo method = target.asMethod();
return String.format("%s#%s", method.declaringClass(), method);
}
return target.asField().toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package io.quarkus.arc.deployment;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
Expand Down Expand Up @@ -235,6 +238,26 @@ public void transform(TransformationContext ctx) {
}));
}

/**
* @param buildTimeConditions the build time conditions from which the excluded classes are extracted.
* @return an instance of {@link BuildExclusionsBuildItem} containing the set of classes
* that have been annotated with unsuccessful build time conditions.
*/
@BuildStep
BuildExclusionsBuildItem buildExclusions(List<PreAdditionalBeanBuildTimeConditionBuildItem> buildTimeConditions) {
final Map<Kind, Set<String>> map = buildTimeConditions.stream()
.filter(item -> !item.isEnabled())
.map(PreAdditionalBeanBuildTimeConditionBuildItem::getTarget)
.collect(
Collectors.groupingBy(
AnnotationTarget::kind,
Collectors.mapping(BuildExclusionsBuildItem::targetMapper, Collectors.toSet())));
return new BuildExclusionsBuildItem(
map.getOrDefault(AnnotationTarget.Kind.CLASS, Collections.emptySet()),
map.getOrDefault(AnnotationTarget.Kind.METHOD, Collections.emptySet()),
map.getOrDefault(AnnotationTarget.Kind.FIELD, Collections.emptySet()));
}

private String toUniqueString(FieldInfo field) {
return field.declaringClass().name().toString() + "." + field.name();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodParameterInfo;

import io.quarkus.arc.config.ConfigProperties;
import io.quarkus.arc.deployment.ArcConfig;
import io.quarkus.arc.deployment.BuildExclusionsBuildItem;
import io.quarkus.arc.deployment.ConfigPropertyBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
Expand All @@ -38,6 +40,7 @@ public class ConfigPropertiesBuildStep {

@BuildStep
void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcConfig arcConfig,
BuildExclusionsBuildItem exclusionsBuildItem,
BuildProducer<ConfigPropertiesMetadataBuildItem> configPropertiesMetadataProducer) {

IndexView index = combinedIndex.getIndex();
Expand All @@ -46,9 +49,13 @@ void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcCo
Map<DotName, Boolean> failOnMismatchingMembers = new HashMap<>();

// handle @ConfigProperties
final Set<String> excludedDeclaringClasses = exclusionsBuildItem.getExcludedDeclaringClasses();
for (AnnotationInstance instance : index.getAnnotations(DotNames.CONFIG_PROPERTIES)) {
ClassInfo classInfo = instance.target().asClass();

final AnnotationTarget target = instance.target();
if (exclusionsBuildItem.isExcluded(target)) {
continue;
}
ClassInfo classInfo = target.asClass();
ConfigProperties.NamingStrategy namingStrategy = getNamingStrategy(arcConfig, instance.value("namingStrategy"));
namingStrategies.put(classInfo.name(), namingStrategy);

Expand All @@ -63,12 +70,16 @@ void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcCo
// handle @ConfigPrefix
for (AnnotationInstance instance : index.getAnnotations(DotNames.CONFIG_PREFIX)) {
ClassInfo classInfo;
if (instance.target().kind() == AnnotationTarget.Kind.FIELD) {
classInfo = index.getClassByName(instance.target().asField().type().name());
} else if (instance.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
short position = instance.target().asMethodParameter().position();
classInfo = index
.getClassByName(instance.target().asMethodParameter().method().parameters().get(position).name());
final AnnotationTarget target = instance.target();
if (exclusionsBuildItem.isExcluded(target)) {
continue;
}
if (target.kind() == AnnotationTarget.Kind.FIELD) {
classInfo = index.getClassByName(target.asField().type().name());
} else if (target.kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
final MethodParameterInfo parameter = target.asMethodParameter();
short position = parameter.position();
classInfo = index.getClassByName(parameter.method().parameters().get(position).name());
} else {
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import org.eclipse.microprofile.config.inject.ConfigProperty;
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.DefaultBean;
import io.quarkus.arc.config.ConfigPrefix;
import io.quarkus.arc.config.ConfigProperties;
import io.quarkus.arc.profile.IfBuildProfile;
import io.quarkus.test.QuarkusUnitTest;

Expand All @@ -32,15 +36,26 @@ public class IfBuildProfileTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Producer.class, OtherProducer.class, AnotherProducer.class,
GreetingBean.class, Hello.class, PingBean.class, PongBean.class, FooBean.class, BarBean.class,
TestInterceptor.class, ProdInterceptor.class, Logging.class));

TestInterceptor.class, ProdInterceptor.class, Logging.class, DummyTestBean.class,
DummyProdBean.class)
.addAsResource(
new StringAsset(
"%test.dummy.message=Hi from Test\n" +
"%test.dummy.complex.message=Hi from complex Test\n" +
"%test.dummy.complex.bis.message=Hi from complex bis Test\n"),
"application.properties"));
@Inject
Hello hello;

@Inject
Instance<BarBean> barBean;

@Inject
DummyTestBean dummyTest;

@Inject
Instance<DummyProdBean> dummyProd;

@Test
public void testInjection() {
assertFalse(TestInterceptor.INTERCEPTED.get());
Expand All @@ -52,13 +67,68 @@ public void testInjection() {
assertTrue(barBean.isUnsatisfied());
assertTrue(TestInterceptor.INTERCEPTED.get());
assertFalse(ProdInterceptor.INTERCEPTED.get());
assertEquals("Hi from Test", dummyTest.message);
assertEquals("Hi from Test", dummyTest.dummy.message);
assertEquals("Hi from complex Test", dummyTest.dummyComplex.message);
assertEquals("Hi from complex bis Test", dummyTest.dummyComplexBis.message);
assertTrue(dummyTest.dummyProd.isUnsatisfied());
assertTrue(dummyProd.isUnsatisfied());
assertTrue(hello.dummyAbsent().isUnsatisfied());
assertTrue(hello.dummyAbsentBis().isUnsatisfied());
}

@Test
public void testSelect() {
assertEquals("hello from test. Foo is: foo from test", CDI.current().select(GreetingBean.class).get().greet());
}

@ConfigProperties
public static class Dummy {
public String message;
}

@Singleton
@IfBuildProfile("test")
static class DummyTestBean {
@ConfigProperty(name = "dummy.message")
String message;
@Inject
Dummy dummy;
@ConfigPrefix("dummy.complex")
Dummy dummyComplex;
Dummy dummyComplexBis;
@Inject
Instance<DummyProd> dummyProd;

@Inject
public void setDummyComplexBis(@ConfigPrefix("dummy.complex.bis") Dummy dummyComplexBis) {
this.dummyComplexBis = dummyComplexBis;
}
}

@IfBuildProfile("prod")
@ConfigProperties(prefix = "dummy.prod")
public static class DummyProd {
public String message;
}

@Singleton
@IfBuildProfile("prod")
static class DummyProdBean {
@ConfigProperty(name = "dummy.message")
String message;
@ConfigPrefix("dummy.absent") // Should not make it fail as excluded by the IfBuildProfile annotation
Dummy dummyAbsent;
Dummy dummyAbsentBis;
@Inject
DummyProd dummyProd;

@Inject
public void setDummyAbsentBis(@ConfigPrefix("dummy.absent.bis") Dummy dummyAbsentBis) { // Should not make it fail as excluded by the IfBuildProfile annotation
this.dummyAbsentBis = dummyAbsentBis;
}
}

@Logging
@ApplicationScoped
static class Hello {
Expand Down Expand Up @@ -91,6 +161,25 @@ String foo() {
return fooBean.foo();
}

Instance<Dummy> dummyAbsent;

@IfBuildProfile("prod")
@ConfigPrefix("dummy.absent.bis")
Instance<Dummy> dummyAbsentBis;

@IfBuildProfile("prod")
@Inject
public void setDummyAbsent(@ConfigPrefix("dummy.absent") Instance<Dummy> dummyAbsent) {
this.dummyAbsent = dummyAbsent;
}

Instance<Dummy> dummyAbsent() {
return dummyAbsent;
}

Instance<Dummy> dummyAbsentBis() {
return dummyAbsentBis;
}
}

@DefaultBean
Expand Down
Loading