Skip to content

Commit cd343de

Browse files
committed
Add ability to ignore missing setters in @ConfigProperties objects
1 parent cba688d commit cd343de

File tree

7 files changed

+176
-13
lines changed

7 files changed

+176
-13
lines changed

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ClassConfigPropertiesUtil.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ static void generateStartupObserverThatInjectsConfigClass(ClassOutput classOutpu
123123
*/
124124
static boolean addProducerMethodForClassConfigProperties(ClassLoader classLoader, ClassInfo configPropertiesClassInfo,
125125
ClassCreator producerClassCreator, String prefixStr, ConfigProperties.NamingStrategy namingStrategy,
126+
boolean failOnMismatchingMember,
126127
boolean needsQualifier, IndexView applicationIndex,
127128
BuildProducer<ConfigPropertyBuildItem> configProperties) {
128129

@@ -177,7 +178,7 @@ static boolean addProducerMethodForClassConfigProperties(ClassLoader classLoader
177178
}
178179

179180
ResultHandle configObject = populateConfigObject(classLoader, configPropertiesClassInfo, prefixStr, namingStrategy,
180-
methodCreator, applicationIndex, configProperties);
181+
failOnMismatchingMember, methodCreator, applicationIndex, configProperties);
181182

182183
if (needsValidation) {
183184
createValidationCodePath(methodCreator, configObject, prefixStr);
@@ -207,7 +208,8 @@ private static boolean isHibernateValidatorInClasspath() {
207208
}
208209

209210
private static ResultHandle populateConfigObject(ClassLoader classLoader, ClassInfo configClassInfo, String prefixStr,
210-
ConfigProperties.NamingStrategy namingStrategy, MethodCreator methodCreator, IndexView applicationIndex,
211+
ConfigProperties.NamingStrategy namingStrategy, boolean failOnMismatchingMember, MethodCreator methodCreator,
212+
IndexView applicationIndex,
211213
BuildProducer<ConfigPropertyBuildItem> configProperties) {
212214
String configObjectClassStr = configClassInfo.name().toString();
213215
ResultHandle configObject = methodCreator.newInstance(MethodDescriptor.ofConstructor(configObjectClassStr));
@@ -242,9 +244,15 @@ private static ResultHandle populateConfigObject(ClassLoader classLoader, ClassI
242244
MethodInfo setter = currentClassInHierarchy.method(setterName, fieldType);
243245
if (setter == null) {
244246
if (!Modifier.isPublic(field.flags()) || Modifier.isFinal(field.flags())) {
245-
throw new IllegalArgumentException(
246-
"Configuration properties class '" + configClassInfo + "' does not have a setter for field "
247-
+ field + " nor is the field a public non-final field");
247+
String message = "Configuration properties class '" + configClassInfo
248+
+ "' does not have a setter for field '"
249+
+ field.name() + "' nor is the field a public non-final field.";
250+
if (failOnMismatchingMember) {
251+
throw new IllegalArgumentException(message);
252+
} else {
253+
LOGGER.warn(message + " It will therefore be ignored.");
254+
continue;
255+
}
248256
}
249257
useFieldAccess = true;
250258
}
@@ -258,7 +266,6 @@ private static ResultHandle populateConfigObject(ClassLoader classLoader, ClassI
258266
* What we do is simply recursively build it up based by adding the field name to the config name prefix
259267
*/
260268
DotName fieldTypeDotName = fieldType.name();
261-
String fieldTypeStr = fieldTypeDotName.toString();
262269
ClassInfo fieldTypeClassInfo = applicationIndex.getClassByName(fieldType.name());
263270
if (fieldTypeClassInfo != null) {
264271
if (!fieldTypeClassInfo.hasNoArgsConstructor()) {
@@ -272,7 +279,8 @@ private static ResultHandle populateConfigObject(ClassLoader classLoader, ClassI
272279
}
273280

274281
ResultHandle nestedConfigObject = populateConfigObject(classLoader, fieldTypeClassInfo,
275-
prefixStr + "." + namingStrategy.getName(field.name()), namingStrategy, methodCreator,
282+
prefixStr + "." + namingStrategy.getName(field.name()), namingStrategy, failOnMismatchingMember,
283+
methodCreator,
276284
applicationIndex, configProperties);
277285
createWriteValue(methodCreator, configObject, field, setter, useFieldAccess, nestedConfigObject);
278286

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesBuildStep.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcCo
3939
IndexView index = combinedIndex.getIndex();
4040

4141
Map<DotName, ConfigProperties.NamingStrategy> namingStrategies = new HashMap<>();
42+
Map<DotName, Boolean> failOnMismatchingMembers = new HashMap<>();
4243

4344
// handle @ConfigProperties
4445
for (AnnotationInstance instance : index.getAnnotations(DotNames.CONFIG_PROPERTIES)) {
@@ -47,8 +48,12 @@ void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcCo
4748
ConfigProperties.NamingStrategy namingStrategy = getNamingStrategy(arcConfig, instance.value("namingStrategy"));
4849
namingStrategies.put(classInfo.name(), namingStrategy);
4950

51+
boolean failOnMismatchingMember = isFailOnMissingMember(instance);
52+
failOnMismatchingMembers.put(classInfo.name(), failOnMismatchingMember);
53+
5054
configPropertiesMetadataProducer
51-
.produce(new ConfigPropertiesMetadataBuildItem(classInfo, getPrefix(instance), namingStrategy, false));
55+
.produce(new ConfigPropertiesMetadataBuildItem(classInfo, getPrefix(instance), namingStrategy,
56+
failOnMismatchingMember, false));
5257
}
5358

5459
// handle @ConfigPrefix
@@ -70,10 +75,18 @@ void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, ArcCo
7075

7176
configPropertiesMetadataProducer
7277
.produce(new ConfigPropertiesMetadataBuildItem(classInfo, instance.value().asString(),
73-
namingStrategy, true));
78+
namingStrategy, failOnMismatchingMembers.getOrDefault(classInfo.name(),
79+
ConfigProperties.DEFAULT_FAIL_ON_MISMATCHING_MEMBER),
80+
true));
7481
}
7582
}
7683

84+
private boolean isFailOnMissingMember(AnnotationInstance instance) {
85+
AnnotationValue failOnMissingMemberValue = instance.value("failOnMismatchingMember");
86+
return failOnMissingMemberValue != null ? failOnMissingMemberValue.asBoolean()
87+
: ConfigProperties.DEFAULT_FAIL_ON_MISMATCHING_MEMBER;
88+
}
89+
7790
private ConfigProperties.NamingStrategy getNamingStrategy(ArcConfig arcConfig, AnnotationValue namingStrategyValue) {
7891
return namingStrategyValue == null ? arcConfig.configPropertiesDefaultNamingStrategy
7992
: ConfigProperties.NamingStrategy.valueOf(namingStrategyValue.asEnum());
@@ -134,7 +147,7 @@ void setup(CombinedIndexBuildItem combinedIndex,
134147
boolean needsValidation = ClassConfigPropertiesUtil.addProducerMethodForClassConfigProperties(
135148
Thread.currentThread().getContextClassLoader(), classInfo, producerClassCreator,
136149
configPropertiesMetadata.getPrefix(), configPropertiesMetadata.getNamingStrategy(),
137-
configPropertiesMetadata.isNeedsQualifier(),
150+
configPropertiesMetadata.isFailOnMismatchingMember(), configPropertiesMetadata.isNeedsQualifier(),
138151
combinedIndex.getIndex(), configProperties);
139152
if (needsValidation) {
140153
configClassesThatNeedValidation.add(classInfo.name());

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesMetadataBuildItem.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ public final class ConfigPropertiesMetadataBuildItem extends MultiBuildItem {
1616
private final ClassInfo classInfo;
1717
private final String prefix;
1818
private final ConfigProperties.NamingStrategy namingStrategy;
19+
private final boolean failOnMismatchingMember;
1920
private final boolean needsQualifier;
2021

2122
public ConfigPropertiesMetadataBuildItem(ClassInfo classInfo, String prefix,
22-
ConfigProperties.NamingStrategy namingStrategy, boolean needsQualifier) {
23+
ConfigProperties.NamingStrategy namingStrategy, boolean failOnMismatchingMember, boolean needsQualifier) {
2324
this.classInfo = classInfo;
2425
this.prefix = sanitisePrefix(prefix);
2526
this.namingStrategy = namingStrategy;
27+
this.failOnMismatchingMember = failOnMismatchingMember;
2628
this.needsQualifier = needsQualifier;
2729
}
2830

@@ -38,6 +40,10 @@ public ConfigProperties.NamingStrategy getNamingStrategy() {
3840
return namingStrategy;
3941
}
4042

43+
public boolean isFailOnMismatchingMember() {
44+
return failOnMismatchingMember;
45+
}
46+
4147
public boolean isNeedsQualifier() {
4248
return needsQualifier;
4349
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.quarkus.arc.test.configproperties;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.Arrays;
6+
import java.util.List;
7+
8+
import javax.inject.Inject;
9+
import javax.inject.Singleton;
10+
11+
import org.jboss.shrinkwrap.api.ShrinkWrap;
12+
import org.jboss.shrinkwrap.api.asset.StringAsset;
13+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.RegisterExtension;
16+
17+
import io.quarkus.arc.config.ConfigProperties;
18+
import io.quarkus.test.QuarkusUnitTest;
19+
20+
public class ClassWithAllowedMissingSetterConfigPropertiesTest {
21+
22+
@RegisterExtension
23+
static final QuarkusUnitTest config = new QuarkusUnitTest()
24+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
25+
.addClasses(DummyBean.class, DummyProperties.class)
26+
.addAsResource(new StringAsset(
27+
"dummy.name=quarkus\ndummy.nested.numbers2=1,2\ndummy.unused=whatever"),
28+
"application.properties"));
29+
30+
@Inject
31+
DummyBean dummyBean;
32+
33+
@Test
34+
public void testConfiguredValues() {
35+
assertEquals("quarkus", dummyBean.dummyProperties.name);
36+
assertEquals(Arrays.asList(1, 2), dummyBean.dummyProperties.nested.numbers2);
37+
}
38+
39+
@Singleton
40+
public static class DummyBean {
41+
@Inject
42+
DummyProperties dummyProperties;
43+
}
44+
45+
@ConfigProperties(prefix = "dummy", failOnMismatchingMember = false)
46+
public static class DummyProperties {
47+
48+
private String name;
49+
private List<Integer> numbers;
50+
private NestedDummyProperties nested;
51+
52+
public void setName(String name) {
53+
this.name = name;
54+
}
55+
56+
public void setNested(NestedDummyProperties nested) {
57+
this.nested = nested;
58+
}
59+
}
60+
61+
public static class NestedDummyProperties extends ParentOfNestedDummyProperties {
62+
private String name2;
63+
private List<Integer> numbers2;
64+
65+
public void setNumbers2(List<Integer> numbers2) {
66+
this.numbers2 = numbers2;
67+
}
68+
}
69+
70+
public static class ParentOfNestedDummyProperties {
71+
private String whatever;
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.quarkus.arc.test.configproperties;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
import static org.junit.jupiter.api.Assertions.fail;
6+
7+
import java.util.List;
8+
9+
import javax.inject.Inject;
10+
import javax.inject.Singleton;
11+
12+
import org.jboss.shrinkwrap.api.ShrinkWrap;
13+
import org.jboss.shrinkwrap.api.asset.StringAsset;
14+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.RegisterExtension;
17+
18+
import io.quarkus.arc.config.ConfigProperties;
19+
import io.quarkus.test.QuarkusUnitTest;
20+
21+
public class ClassWithNotAllowedMissingSetterConfigPropertiesTest {
22+
23+
@RegisterExtension
24+
static final QuarkusUnitTest config = new QuarkusUnitTest()
25+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
26+
.addClasses(DummyBean.class, DummyProperties.class)
27+
.addAsResource(new StringAsset(
28+
"dummy.name=quarkus\ndummy.unused=whatever"),
29+
"application.properties"))
30+
.assertException(e -> {
31+
assertEquals(IllegalArgumentException.class, e.getClass());
32+
assertTrue(e.getMessage().contains("numbers"));
33+
});
34+
35+
@Test
36+
public void shouldNotBeInvoked() {
37+
// This method should not be invoked
38+
fail();
39+
}
40+
41+
@Singleton
42+
public static class DummyBean {
43+
@Inject
44+
DummyProperties dummyProperties;
45+
}
46+
47+
@ConfigProperties(prefix = "dummy")
48+
public static class DummyProperties {
49+
50+
private String name;
51+
private List<Integer> numbers;
52+
53+
public void setName(String name) {
54+
this.name = name;
55+
}
56+
}
57+
}

extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigProperties.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
public @interface ConfigProperties {
1717

1818
String UNSET_PREFIX = "<< unset >>";
19+
boolean DEFAULT_FAIL_ON_MISMATCHING_MEMBER = true;
1920

2021
/**
2122
* If the default is used, the class name will be used to determine the proper prefix
@@ -49,6 +50,11 @@
4950
*/
5051
NamingStrategy namingStrategy() default NamingStrategy.FROM_CONFIG;
5152

53+
/**
54+
* Whether or not to fail when a non-public field of a class doesn't have a corresponding setter
55+
*/
56+
boolean failOnMismatchingMember() default DEFAULT_FAIL_ON_MISMATCHING_MEMBER;
57+
5258
enum NamingStrategy {
5359
FROM_CONFIG {
5460
@Override

extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadata(Annotat
4545

4646
private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadataFromClass(AnnotationInstance annotation) {
4747
return new ConfigPropertiesMetadataBuildItem(annotation.target().asClass(), getPrefix(annotation),
48-
ConfigProperties.NamingStrategy.VERBATIM, false);
48+
ConfigProperties.NamingStrategy.VERBATIM, true, false);
4949
}
5050

5151
private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadataFromMethod(AnnotationInstance annotation,
5252
IndexView index) {
5353
return new ConfigPropertiesMetadataBuildItem(index.getClassByName(annotation.target().asMethod().returnType().name()),
54-
getPrefix(annotation), ConfigProperties.NamingStrategy.VERBATIM, false);
54+
getPrefix(annotation), ConfigProperties.NamingStrategy.VERBATIM, true, false);
5555
}
5656

5757
private String getPrefix(AnnotationInstance annotation) {

0 commit comments

Comments
 (0)