Commit 35875c7f authored by Stephane Nicoll's avatar Stephane Nicoll

Merge manual item meta-data

Previously, manual meta-data were added to the existing set of entries
which could lead to duplicates if a manual entry is meant to complement
a property that is detected via the processor.

We now match the name and type of the item against the auto-detected
entries. If no match is found, we add the extra entry as we did before.
If a match is found we override the description, default value and
deprecation information.

Closes gh-3562
parent 4235df18
...@@ -752,11 +752,17 @@ if it were nested. ...@@ -752,11 +752,17 @@ if it were nested.
[[configuration-metadata-additional-metadata]] [[configuration-metadata-additional-metadata]]
==== Adding additional meta-data ==== Adding additional meta-data
Spring Boot's configuration file handling is quite flexible; and it often the case that Spring Boot's configuration file handling is quite flexible; and it is often the case
properties may exist that are not bound to a `@ConfigurationProperties` bean. To support that properties may exist that are not bound to a `@ConfigurationProperties` bean. You
such cases and allow you to provide custom "hints", the annotation processor will may also need to tune some attributes of an existing key. To support such cases and allow
automatically merge items from `META-INF/additional-spring-configuration-metadata.json` you to provide custom "hints", the annotation processor will automatically merge items
into the main meta-data file. from `META-INF/additional-spring-configuration-metadata.json` into the main meta-data
file.
If you refer to a property that has been detected automatically, the description,
default value and deprecation information are overridden if specified. If the manual
property declaration is not identified in the current module, it is added as a brand new
property.
The format of the `additional-spring-configuration-metadata.json` file is exactly the same The format of the `additional-spring-configuration-metadata.json` file is exactly the same
as the regular `spring-configuration-metadata.json`. The additional properties file is as the regular `spring-configuration-metadata.json`. The additional properties file is
......
...@@ -374,7 +374,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -374,7 +374,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata) { private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata) {
try { try {
ConfigurationMetadata merged = new ConfigurationMetadata(metadata); ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
merged.addAll(this.metadataStore.readAdditionalMetadata()); merged.merge(this.metadataStore.readAdditionalMetadata());
return merged; return merged;
} }
catch (FileNotFoundException ex) { catch (FileNotFoundException ex) {
......
...@@ -19,9 +19,15 @@ package org.springframework.boot.configurationprocessor.metadata; ...@@ -19,9 +19,15 @@ package org.springframework.boot.configurationprocessor.metadata;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/** /**
* Configuration meta-data. * Configuration meta-data.
* *
...@@ -34,18 +40,18 @@ public class ConfigurationMetadata { ...@@ -34,18 +40,18 @@ public class ConfigurationMetadata {
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])"); private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
private final List<ItemMetadata> items; private final MultiValueMap<String, ItemMetadata> items;
private final List<ItemHint> hints; private final MultiValueMap<String, ItemHint> hints;
public ConfigurationMetadata() { public ConfigurationMetadata() {
this.items = new ArrayList<ItemMetadata>(); this.items = new LinkedMultiValueMap<String, ItemMetadata>();
this.hints = new ArrayList<ItemHint>(); this.hints = new LinkedMultiValueMap<String, ItemHint>();
} }
public ConfigurationMetadata(ConfigurationMetadata metadata) { public ConfigurationMetadata(ConfigurationMetadata metadata) {
this.items = new ArrayList<ItemMetadata>(metadata.getItems()); this.items = new LinkedMultiValueMap<String, ItemMetadata>(metadata.items);
this.hints = new ArrayList<ItemHint>(metadata.getHints()); this.hints = new LinkedMultiValueMap<String, ItemHint>(metadata.hints);
} }
/** /**
...@@ -53,40 +59,97 @@ public class ConfigurationMetadata { ...@@ -53,40 +59,97 @@ public class ConfigurationMetadata {
* @param itemMetadata the meta-data to add * @param itemMetadata the meta-data to add
*/ */
public void add(ItemMetadata itemMetadata) { public void add(ItemMetadata itemMetadata) {
this.items.add(itemMetadata); this.items.add(itemMetadata.getName(), itemMetadata);
Collections.sort(this.items);
} }
/**
* Add item hint.
* @param itemHint the item hint to add
*/
public void add(ItemHint itemHint) { public void add(ItemHint itemHint) {
this.hints.add(itemHint); this.hints.add(itemHint.getName(), itemHint);
Collections.sort(this.hints);
} }
/** /**
* Add all properties from another {@link ConfigurationMetadata}. * Merge the content from another {@link ConfigurationMetadata}.
* @param metadata the {@link ConfigurationMetadata} instance to merge * @param metadata the {@link ConfigurationMetadata} instance to merge
*/ */
public void addAll(ConfigurationMetadata metadata) { public void merge(ConfigurationMetadata metadata) {
this.items.addAll(metadata.getItems()); for (ItemMetadata additionalItem : metadata.getItems()) {
Collections.sort(this.items); mergeItemMetadata(additionalItem);
this.hints.addAll(metadata.getHints()); }
Collections.sort(this.hints); for (ItemHint itemHint : metadata.getHints()) {
add(itemHint);
}
} }
/** /**
* @return the meta-data properties. * @return the meta-data properties.
*/ */
public List<ItemMetadata> getItems() { public List<ItemMetadata> getItems() {
return Collections.unmodifiableList(this.items); return flattenValues(this.items);
} }
/** /**
* @return the meta-data hints. * @return the meta-data hints.
*/ */
public List<ItemHint> getHints() { public List<ItemHint> getHints() {
return Collections.unmodifiableList(this.hints); return flattenValues(this.hints);
} }
protected void mergeItemMetadata(ItemMetadata metadata) {
ItemMetadata matching = findMatchingItemMetadata(metadata);
if (matching != null) {
if (metadata.getDescription() != null) {
matching.setDescription(metadata.getDescription());
}
if (metadata.getDefaultValue() != null) {
matching.setDefaultValue(metadata.getDefaultValue());
}
ItemDeprecation deprecation = metadata.getDeprecation();
ItemDeprecation matchingDeprecation = matching.getDeprecation();
if (deprecation != null) {
if (matchingDeprecation == null) {
matching.setDeprecation(deprecation);
}
else {
if (deprecation.getReason() != null) {
matchingDeprecation.setReason(deprecation.getReason());
}
if (deprecation.getReplacement() != null) {
matchingDeprecation.setReplacement(deprecation.getReplacement());
}
}
}
}
else {
this.items.add(metadata.getName(), metadata);
}
}
private ItemMetadata findMatchingItemMetadata(ItemMetadata metadata) {
List<ItemMetadata> candidates = this.items.get(metadata.getName());
if (CollectionUtils.isEmpty(candidates)) {
return null;
}
ListIterator<ItemMetadata> it = candidates.listIterator();
while (it.hasNext()) {
if (!it.next().hasSameType(metadata)) {
it.remove();
}
}
if (candidates.size() == 1) {
return candidates.get(0);
}
for (ItemMetadata candidate : candidates) {
if (ObjectUtils.nullSafeEquals(candidate.getSourceType(), metadata.getSourceType())) {
return candidate;
}
}
return null;
}
public static String nestedPrefix(String prefix, String name) { public static String nestedPrefix(String prefix, String name) {
String nestedPrefix = (prefix == null ? "" : prefix); String nestedPrefix = (prefix == null ? "" : prefix);
String dashedName = toDashedCase(name); String dashedName = toDashedCase(name);
...@@ -114,4 +177,13 @@ public class ConfigurationMetadata { ...@@ -114,4 +177,13 @@ public class ConfigurationMetadata {
return first + "-" + second; return first + "-" + second;
} }
private static <T extends Comparable> List<T> flattenValues(MultiValueMap<?, T> map) {
List<T> content = new ArrayList<T>();
for (List<T> values : map.values()) {
content.addAll(values);
}
Collections.sort(content);
return content;
}
} }
...@@ -26,21 +26,21 @@ package org.springframework.boot.configurationprocessor.metadata; ...@@ -26,21 +26,21 @@ package org.springframework.boot.configurationprocessor.metadata;
*/ */
public class ItemMetadata implements Comparable<ItemMetadata> { public class ItemMetadata implements Comparable<ItemMetadata> {
private final ItemType itemType; private ItemType itemType;
private final String name; private String name;
private final String type; private String type;
private final String description; private String description;
private final String sourceType; private String sourceType;
private final String sourceMethod; private String sourceMethod;
private final Object defaultValue; private Object defaultValue;
private final ItemDeprecation deprecation; private ItemDeprecation deprecation;
ItemMetadata(ItemType itemType, String prefix, String name, String type, ItemMetadata(ItemType itemType, String prefix, String name, String type,
String sourceType, String sourceMethod, String description, String sourceType, String sourceMethod, String description,
...@@ -72,34 +72,66 @@ public class ItemMetadata implements Comparable<ItemMetadata> { ...@@ -72,34 +72,66 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
return this.itemType == itemType; return this.itemType == itemType;
} }
public boolean hasSameType(ItemMetadata metadata) {
return this.itemType == metadata.itemType;
}
public String getName() { public String getName() {
return this.name; return this.name;
} }
public void setName(String name) {
this.name = name;
}
public String getType() { public String getType() {
return this.type; return this.type;
} }
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSourceType() { public String getSourceType() {
return this.sourceType; return this.sourceType;
} }
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
public String getSourceMethod() { public String getSourceMethod() {
return this.sourceMethod; return this.sourceMethod;
} }
public String getDescription() { public void setSourceMethod(String sourceMethod) {
return this.description; this.sourceMethod = sourceMethod;
} }
public Object getDefaultValue() { public Object getDefaultValue() {
return this.defaultValue; return this.defaultValue;
} }
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public ItemDeprecation getDeprecation() { public ItemDeprecation getDeprecation() {
return this.deprecation; return this.deprecation;
} }
public void setDeprecation(ItemDeprecation deprecation) {
this.deprecation = deprecation;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder string = new StringBuilder(this.name); StringBuilder string = new StringBuilder(this.name);
......
...@@ -348,16 +348,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -348,16 +348,9 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test @Test
public void mergingOfAdditionalProperty() throws Exception { public void mergingOfAdditionalProperty() throws Exception {
File additionalMetadataFile = createAdditionalMetadataFile(); ItemMetadata property = ItemMetadata.newProperty(null, "foo",
JSONObject property = new JSONObject(); "java.lang.String", AdditionalMetadata.class.getName(), null, null, null,null);
property.put("name", "foo"); writeAdditionalMetadata(property);
property.put("type", "java.lang.String");
property.put("sourceType", AdditionalMetadata.class.getName());
JSONArray properties = new JSONArray();
properties.put(property);
JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("properties", properties);
writeMetadata(additionalMetadataFile, additionalMetadata);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(metadata, containsProperty("simple.comparator")); assertThat(metadata, containsProperty("simple.comparator"));
assertThat(metadata, assertThat(metadata,
...@@ -365,6 +358,65 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -365,6 +358,65 @@ public class ConfigurationMetadataAnnotationProcessorTests {
.fromSource(AdditionalMetadata.class)); .fromSource(AdditionalMetadata.class));
} }
@Test
public void mergeExistingPropertyDefaultValue() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "flag", null,
null, null, null, true, null);
writeAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(
metadata,
containsProperty("simple.flag", Boolean.class)
.fromSource(SimpleProperties.class)
.withDescription("A simple flag.")
.withDefaultValue(is(true)));
assertThat(metadata.getItems().size(), is(4));
}
@Test
public void mergeExistingPropertyDescription() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null,
null, null, "A nice comparator.", null, null);
writeAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(
metadata,
containsProperty("simple.comparator", "java.util.Comparator<?>")
.fromSource(SimpleProperties.class)
.withDescription("A nice comparator."));
assertThat(metadata.getItems().size(), is(4));
}
@Test
public void mergeExistingPropertyDeprecation() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null,
null, null, null, null, new ItemDeprecation("Don't use this.",
"simple.complex-comparator"));
writeAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(
metadata,
containsProperty("simple.comparator", "java.util.Comparator<?>")
.fromSource(SimpleProperties.class)
.withDeprecation("Don't use this.", "simple.complex-comparator"));
assertThat(metadata.getItems().size(), is(4));
}
@Test
public void mergeExistingPropertyDeprecationOverride() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null,
null, null, null, null, new ItemDeprecation("Don't use this.",
"single.name"));
writeAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(DeprecatedSingleProperty.class);
assertThat(
metadata,
containsProperty("singledeprecated.name", String.class.getName())
.fromSource(DeprecatedSingleProperty.class)
.withDeprecation("Don't use this.", "single.name"));
assertThat(metadata.getItems().size(), is(3));
}
@Test @Test
public void mergeOfInvalidAdditionalMetadata() throws IOException { public void mergeOfInvalidAdditionalMetadata() throws IOException {
File additionalMetadataFile = createAdditionalMetadataFile(); File additionalMetadataFile = createAdditionalMetadataFile();
...@@ -532,6 +584,13 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -532,6 +584,13 @@ public class ConfigurationMetadataAnnotationProcessorTests {
return processor.getMetadata(); return processor.getMetadata();
} }
private void writeAdditionalMetadata(ItemMetadata... metadata) throws IOException {
File additionalMetadataFile = createAdditionalMetadataFile();
JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("properties", metadata);
writeMetadata(additionalMetadataFile, additionalMetadata);
}
private void writeAdditionalHints(ItemHint... hints) throws IOException { private void writeAdditionalHints(ItemHint... hints) throws IOException {
File additionalMetadataFile = createAdditionalMetadataFile(); File additionalMetadataFile = createAdditionalMetadataFile();
JSONObject additionalMetadata = new JSONObject(); JSONObject additionalMetadata = new JSONObject();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment