Add ability to ignore configuration properties

Properties which should be ignored can be specified in the
additional-spring-configuration-metadata.json file. The ignored
properties section is copied into the final
spring-configuration-metadata.json file, and the ignored properties are
removed from the properties element in the final file.

Closes gh-2421
This commit is contained in:
Moritz Halbritter
2025-01-21 15:21:44 +01:00
parent 08e9c16f33
commit f24ba9935c
11 changed files with 375 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ import javax.tools.Diagnostic.Kind;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.InvalidConfigurationMetadataException;
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
import org.springframework.boot.configurationprocessor.metadata.ItemIgnore;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
/**
@@ -372,6 +373,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
protected ConfigurationMetadata writeMetadata() throws Exception {
ConfigurationMetadata metadata = this.metadataCollector.getMetadata();
metadata = mergeAdditionalMetadata(metadata);
removeIgnored(metadata);
if (!metadata.getItems().isEmpty()) {
this.metadataStore.writeMetadata(metadata);
return metadata;
@@ -379,6 +381,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return null;
}
private void removeIgnored(ConfigurationMetadata metadata) {
for (ItemIgnore itemIgnore : metadata.getIgnored()) {
metadata.removeMetadata(itemIgnore.getType(), itemIgnore.getName());
}
}
private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata) {
try {
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
/**
@@ -29,6 +30,7 @@ import org.springframework.boot.configurationprocessor.support.ConventionUtils;
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Moritz Halbritter
* @since 1.2.0
* @see ItemMetadata
*/
@@ -38,14 +40,18 @@ public class ConfigurationMetadata {
private final Map<String, List<ItemHint>> hints;
private final Map<String, List<ItemIgnore>> ignored;
public ConfigurationMetadata() {
this.items = new LinkedHashMap<>();
this.hints = new LinkedHashMap<>();
this.ignored = new LinkedHashMap<>();
}
public ConfigurationMetadata(ConfigurationMetadata metadata) {
this.items = new LinkedHashMap<>(metadata.items);
this.hints = new LinkedHashMap<>(metadata.hints);
this.ignored = new LinkedHashMap<>(metadata.ignored);
}
/**
@@ -73,6 +79,32 @@ public class ConfigurationMetadata {
add(this.hints, itemHint.getName(), itemHint, false);
}
/**
* Add item ignore.
* @param itemIgnore the item ignore to add
* @since 3.5.0
*/
public void add(ItemIgnore itemIgnore) {
add(this.ignored, itemIgnore.getName(), itemIgnore, false);
}
/**
* Remove item meta-data for the given item type and name.
* @param itemType the item type
* @param name the name
* @since 3.5.0
*/
public void removeMetadata(ItemType itemType, String name) {
List<ItemMetadata> metadata = this.items.get(name);
if (metadata == null) {
return;
}
metadata.removeIf((item) -> item.isOfItemType(itemType));
if (metadata.isEmpty()) {
this.items.remove(name);
}
}
/**
* Merge the content from another {@link ConfigurationMetadata}.
* @param metadata the {@link ConfigurationMetadata} instance to merge
@@ -84,6 +116,9 @@ public class ConfigurationMetadata {
for (ItemHint itemHint : metadata.getHints()) {
add(itemHint);
}
for (ItemIgnore itemIgnore : metadata.getIgnored()) {
add(itemIgnore);
}
}
/**
@@ -102,6 +137,14 @@ public class ConfigurationMetadata {
return flattenValues(this.hints);
}
/**
* Return ignore meta-data.
* @return the ignores
*/
public List<ItemIgnore> getIgnored() {
return flattenValues(this.ignored);
}
protected void mergeItemMetadata(ItemMetadata metadata) {
ItemMetadata matching = findMatchingItemMetadata(metadata);
if (matching != null) {

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor.metadata;
import java.util.Objects;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
/**
* Ignored item.
*
* @author Moritz Halbritter
* @since 3.5.0
*/
public final class ItemIgnore implements Comparable<ItemIgnore> {
private final ItemType type;
private final String name;
private ItemIgnore(ItemType type, String name) {
if (name == null) {
throw new IllegalArgumentException("'name' must not be null");
}
if (type == null) {
throw new IllegalArgumentException("'type' must not be null");
}
this.type = type;
this.name = name;
}
public String getName() {
return this.name;
}
public ItemType getType() {
return this.type;
}
@Override
public int compareTo(ItemIgnore other) {
return getName().compareTo(other.getName());
}
/**
* Create an ignore for a property with the given name.
* @param name the name
* @return the item ignore
*/
public static ItemIgnore forProperty(String name) {
return new ItemIgnore(ItemType.PROPERTY, name);
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
ItemIgnore that = (ItemIgnore) o;
return this.type == that.type && Objects.equals(this.name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.name);
}
@Override
public String toString() {
return "ItemIgnore{" + "type=" + this.type + ", name='" + this.name + '\'' + '}';
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.Ite
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Moritz Halbritter
*/
class JsonConverter {
@@ -59,6 +60,21 @@ class JsonConverter {
return jsonArray;
}
JSONObject toJsonObject(Collection<ItemIgnore> ignored) throws Exception {
JSONObject result = new JSONObject();
result.put("properties", ignoreToJsonArray(
ignored.stream().filter((itemIgnore) -> itemIgnore.getType() == ItemType.PROPERTY).toList()));
return result;
}
private JSONArray ignoreToJsonArray(Collection<ItemIgnore> ignored) throws Exception {
JSONArray result = new JSONArray();
for (ItemIgnore itemIgnore : ignored) {
result.put(toJsonObject(itemIgnore));
}
return result;
}
JSONObject toJsonObject(ItemMetadata item) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", item.getName());
@@ -103,6 +119,12 @@ class JsonConverter {
return jsonObject;
}
private JSONObject toJsonObject(ItemIgnore ignore) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", ignore.getName());
return jsonObject;
}
private JSONArray getItemHintValues(ItemHint hint) throws Exception {
JSONArray values = new JSONArray();
for (ItemHint.ValueHint value : hint.getValues()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import java.util.Set;
import java.util.TreeSet;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
@@ -50,6 +51,7 @@ public class JsonMarshaller {
object.put("groups", converter.toJsonArray(metadata, ItemType.GROUP));
object.put("properties", converter.toJsonArray(metadata, ItemType.PROPERTY));
object.put("hints", converter.toJsonArray(metadata.getHints()));
object.put("ignored", converter.toJsonObject(metadata.getIgnored()));
outputStream.write(object.toString(2).getBytes(StandardCharsets.UTF_8));
}
catch (Exception ex) {
@@ -67,7 +69,7 @@ public class JsonMarshaller {
ConfigurationMetadata metadata = new ConfigurationMetadata();
JSONObject object = new JSONObject(toString(inputStream));
JsonPath path = JsonPath.root();
checkAllowedKeys(object, path, "groups", "properties", "hints");
checkAllowedKeys(object, path, "groups", "properties", "hints", "ignored");
JSONArray groups = object.optJSONArray("groups");
if (groups != null) {
for (int i = 0; i < groups.length(); i++) {
@@ -88,9 +90,27 @@ public class JsonMarshaller {
metadata.add(toItemHint((JSONObject) hints.get(i), path.resolve("hints").index(i)));
}
}
JSONObject ignored = object.optJSONObject("ignored");
if (ignored != null) {
checkAllowedKeys(ignored, path.resolve("ignored"), "properties");
addIgnoredProperties(metadata, ignored, path.resolve("ignored"));
}
return metadata;
}
private void addIgnoredProperties(ConfigurationMetadata metadata, JSONObject ignored, JsonPath path)
throws JSONException {
JSONArray properties = ignored.optJSONArray("properties");
if (properties == null) {
return;
}
for (int i = 0; i < properties.length(); i++) {
JSONObject jsonObject = properties.getJSONObject(i);
checkAllowedKeys(jsonObject, path.resolve("properties").index(i), "name");
metadata.add(ItemIgnore.forProperty(jsonObject.getString("name")));
}
}
private ItemMetadata toItemMetadata(JSONObject object, JsonPath path, ItemType itemType) throws Exception {
switch (itemType) {
case GROUP -> checkAllowedKeys(object, path, "name", "type", "description", "sourceType", "sourceMethod");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit;
import org.junit.jupiter.api.Test;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemIgnore;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.metadata.Metadata;
import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration;
@@ -38,6 +39,7 @@ import org.springframework.boot.configurationsample.simple.DescriptionProperties
import org.springframework.boot.configurationsample.simple.HierarchicalProperties;
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
import org.springframework.boot.configurationsample.simple.IgnoredProperties;
import org.springframework.boot.configurationsample.simple.InnerClassWithPrivateConstructor;
import org.springframework.boot.configurationsample.simple.NotAnnotated;
import org.springframework.boot.configurationsample.simple.SimpleArrayProperties;
@@ -570,4 +572,24 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
.withDescription("last description in Javadoc"));
}
@Test
void shouldIgnoreProperties() {
String additionalMetadata = """
{
"ignored": {
"properties": [
{
"name": "ignored.prop3"
}
]
}
}
""";
ConfigurationMetadata metadata = compile(additionalMetadata, IgnoredProperties.class);
assertThat(metadata).has(Metadata.withProperty("ignored.prop1", String.class));
assertThat(metadata).has(Metadata.withProperty("ignored.prop2", String.class));
assertThat(metadata).doesNotHave(Metadata.withProperty("ignored.prop3", String.class));
assertThat(metadata.getIgnored()).containsExactly(ItemIgnore.forProperty("ignored.prop3"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatException;
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Moritz Halbritter
*/
class JsonMarshallerTests {
@@ -174,14 +175,36 @@ class JsonMarshallerTests {
"\"java.lang.Boolean\"", "\"com.example.bravo.aaa\"", "\"java.lang.Integer\"", "\"com.example.Bar");
}
@Test
void shouldReadIgnoredProperties() throws Exception {
String json = """
{
"ignored": {
"properties": [
{
"name": "prop1"
},
{
"name": "prop2"
}
]
}
}
""";
ConfigurationMetadata metadata = read(json);
assertThat(metadata.getIgnored()).containsExactly(ItemIgnore.forProperty("prop1"),
ItemIgnore.forProperty("prop2"));
}
@Test
void shouldCheckRootFields() {
String json = """
{
"groups": [], "properties": [], "hints": [], "dummy": []
"groups": [], "properties": [], "hints": [], "ignored": {}, "dummy": []
}""";
assertThatException().isThrownBy(() -> read(json))
.withMessage("Expected only keys [groups, hints, properties], but found additional keys [dummy]. Path: .");
.withMessage(
"Expected only keys [groups, hints, ignored, properties], but found additional keys [dummy]. Path: .");
}
@Test
@@ -324,9 +347,41 @@ class JsonMarshallerTests {
"Expected only keys [name, parameters], but found additional keys [dummy]. Path: .hints.[0].providers.[0]");
}
private void read(String json) throws Exception {
@Test
void shouldCheckIgnoreFields() {
String json = """
{
"ignored": {
"properties": [],
"dummy": {}
}
}
""";
assertThatException().isThrownBy(() -> read(json))
.withMessage("Expected only keys [properties], but found additional keys [dummy]. Path: .ignored");
}
@Test
void shouldCheckIgnorePropertiesFields() {
String json = """
{
"ignored": {
"properties": [
{
"name": "prop1",
"dummy": true
}
]
}
}
""";
assertThatException().isThrownBy(() -> read(json))
.withMessage("Expected only keys [name], but found additional keys [dummy]. Path: .ignored.properties.[0]");
}
private ConfigurationMetadata read(String json) throws Exception {
JsonMarshaller marshaller = new JsonMarshaller();
marshaller.read(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
return marshaller.read(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.simple;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Configuration properties where some of them are being ignored.
*
* @author Moritz Halbritter
*/
@ConfigurationProperties("ignored")
public class IgnoredProperties {
private String prop1;
private String prop2;
private String prop3;
public String getProp1() {
return this.prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
public String getProp2() {
return this.prop2;
}
public void setProp2(String prop2) {
this.prop2 = prop2;
}
public String getProp3() {
return this.prop3;
}
public void setProp3(String prop3) {
this.prop3 = prop3;
}
}