Add support for value provider
Improve the "hints" section of the metadata so that each hint can provide the reference to a value provider. A value provider defines how a tool can discover the potential values of a property based on the context. The provider is identifed by a name and may have an arbitrary number of parameters. Closes gh-3303
This commit is contained in:
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provide hints on an {@link ItemMetadata}. Defines the list of possible values for a
|
||||
@@ -27,7 +28,7 @@ import java.util.List;
|
||||
* <p>
|
||||
* The {@code name} of the hint is the name of the related property with one major
|
||||
* exception for map types as both the keys and values of the map can have hints. In such
|
||||
* a case, the hint should be suffixed by ".key" or ".values" respectively. Creating a
|
||||
* a case, the hint should be suffixed by ".keys" or ".values" respectively. Creating a
|
||||
* hint for a map using its property name is therefore invalid.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
@@ -39,9 +40,12 @@ public class ItemHint implements Comparable<ItemHint> {
|
||||
|
||||
private final List<ValueHint> values;
|
||||
|
||||
public ItemHint(String name, List<ValueHint> values) {
|
||||
private final List<ProviderHint> providers;
|
||||
|
||||
public ItemHint(String name, List<ValueHint> values, List<ProviderHint> providers) {
|
||||
this.name = toCanonicalName(name);
|
||||
this.values = new ArrayList<ValueHint>(values);
|
||||
this.values = (values != null ? new ArrayList<ValueHint>(values) : new ArrayList<ValueHint>());
|
||||
this.providers = (providers != null ? new ArrayList<ProviderHint>(providers) : new ArrayList<ProviderHint>());
|
||||
}
|
||||
|
||||
private String toCanonicalName(String name) {
|
||||
@@ -62,19 +66,23 @@ public class ItemHint implements Comparable<ItemHint> {
|
||||
return Collections.unmodifiableList(this.values);
|
||||
}
|
||||
|
||||
public List<ProviderHint> getProviders() {
|
||||
return Collections.unmodifiableList(this.providers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ItemHint other) {
|
||||
return getName().compareTo(other.getName());
|
||||
}
|
||||
|
||||
public static ItemHint newHint(String name, ValueHint... values) {
|
||||
return new ItemHint(name, Arrays.asList(values));
|
||||
return new ItemHint(name, Arrays.asList(values), Collections.<ProviderHint>emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ItemHint{" + "name='" + this.name + '\'' + ", values=" + this.values
|
||||
+ '}';
|
||||
return "ItemHint{" + "name='" + this.name + ", values=" + this.values
|
||||
+ "providers=" + this.providers + '}';
|
||||
}
|
||||
|
||||
public static class ValueHint {
|
||||
@@ -104,4 +112,28 @@ public class ItemHint implements Comparable<ItemHint> {
|
||||
|
||||
}
|
||||
|
||||
public static class ProviderHint {
|
||||
private final String name;
|
||||
private final Map<String,Object> parameters;
|
||||
|
||||
public ProviderHint(String name, Map<String, Object> parameters) {
|
||||
this.name = name;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Provider{" + "name='" + this.name + ", parameters=" + this.parameters +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ import java.lang.reflect.Array;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.JSONArray;
|
||||
@@ -103,6 +105,22 @@ public class JsonMarshaller {
|
||||
}
|
||||
jsonObject.put("values", valuesArray);
|
||||
}
|
||||
if (!hint.getProviders().isEmpty()) {
|
||||
JSONArray providersArray = new JSONArray();
|
||||
for (ItemHint.ProviderHint providerHint : hint.getProviders()) {
|
||||
JSONObject providerHintObject = new JSONOrderedObject();
|
||||
providerHintObject.put("name", providerHint.getName());
|
||||
if (providerHint.getParameters() != null && !providerHint.getParameters().isEmpty()) {
|
||||
JSONObject parametersObject = new JSONOrderedObject();
|
||||
for (Map.Entry<String, Object> entry : providerHint.getParameters().entrySet()) {
|
||||
parametersObject.put(entry.getKey(), extractItemValue(entry.getValue()));
|
||||
}
|
||||
providerHintObject.put("parameters", parametersObject);
|
||||
}
|
||||
providersArray.put(providerHintObject);
|
||||
}
|
||||
jsonObject.put("providers", providersArray);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@@ -182,7 +200,14 @@ public class JsonMarshaller {
|
||||
values.add(toValueHint((JSONObject) valuesArray.get(i)));
|
||||
}
|
||||
}
|
||||
return new ItemHint(name, values);
|
||||
List<ItemHint.ProviderHint> providers = new ArrayList<ItemHint.ProviderHint>();
|
||||
if (object.has("providers")) {
|
||||
JSONArray providersObject = object.getJSONArray("providers");
|
||||
for (int i = 0; i < providersObject.length(); i++) {
|
||||
providers.add(toProviderHint((JSONObject) providersObject.get(i)));
|
||||
}
|
||||
}
|
||||
return new ItemHint(name, values, providers);
|
||||
}
|
||||
|
||||
private ItemHint.ValueHint toValueHint(JSONObject object) {
|
||||
@@ -191,6 +216,20 @@ public class JsonMarshaller {
|
||||
return new ItemHint.ValueHint(value, description);
|
||||
}
|
||||
|
||||
private ItemHint.ProviderHint toProviderHint(JSONObject object) {
|
||||
String name = object.getString("name");
|
||||
Map<String,Object> parameters = new HashMap<String,Object>();
|
||||
if (object.has("parameters")) {
|
||||
JSONObject parametersObject = object.getJSONObject("parameters");
|
||||
for (Object k : parametersObject.keySet()) {
|
||||
String key = (String) k;
|
||||
Object value = readItemValue(parametersObject.get(key));
|
||||
parameters.put(key, value);
|
||||
}
|
||||
}
|
||||
return new ItemHint.ProviderHint(name, parameters);
|
||||
}
|
||||
|
||||
private Object readItemValue(Object value) {
|
||||
if (value instanceof JSONArray) {
|
||||
JSONArray array = (JSONArray) value;
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.springframework.boot.configurationprocessor;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -369,6 +371,20 @@ public class ConfigurationMetadataAnnotationProcessorTests {
|
||||
containsHint("simple.the-name").withValue(0, "boot", "Bla bla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergingOfHintWithProvider() throws Exception {
|
||||
writeAdditionalHints(
|
||||
new ItemHint("simple.theName", Collections.<ItemHint.ValueHint>emptyList(), Arrays.asList(
|
||||
new ItemHint.ProviderHint("first", Collections.<String,Object>singletonMap("target", "org.foo")),
|
||||
new ItemHint.ProviderHint("second", null))
|
||||
));
|
||||
|
||||
ConfigurationMetadata metadata = compile(SimpleProperties.class);
|
||||
assertThat(metadata, containsHint("simple.the-name")
|
||||
.withProvider("first", "target", "org.foo")
|
||||
.withProvider("second"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incrementalBuild() throws Exception {
|
||||
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.collection.IsMapContaining;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
@@ -206,13 +209,16 @@ public class ConfigurationMetadataMatchers {
|
||||
|
||||
private final List<ValueHintMatcher> values;
|
||||
|
||||
private final List<ProviderHintMatcher> providers;
|
||||
|
||||
public ContainsHintMatcher(String name) {
|
||||
this(name, new ArrayList<ValueHintMatcher>());
|
||||
this(name, new ArrayList<ValueHintMatcher>(), new ArrayList<ProviderHintMatcher>());
|
||||
}
|
||||
|
||||
public ContainsHintMatcher(String name, List<ValueHintMatcher> values) {
|
||||
public ContainsHintMatcher(String name, List<ValueHintMatcher> values, List<ProviderHintMatcher> providers) {
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,6 +236,11 @@ public class ConfigurationMetadataMatchers {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (ProviderHintMatcher provider : this.providers) {
|
||||
if (!provider.matches(itemHint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -251,12 +262,29 @@ public class ConfigurationMetadataMatchers {
|
||||
if (this.values != null) {
|
||||
description.appendText(" values ").appendValue(this.values);
|
||||
}
|
||||
if (this.providers != null) {
|
||||
description.appendText(" providers ").appendValue(this.providers);
|
||||
}
|
||||
}
|
||||
|
||||
public ContainsHintMatcher withValue(int index, Object value, String description) {
|
||||
List<ValueHintMatcher> values = new ArrayList<ValueHintMatcher>(this.values);
|
||||
values.add(new ValueHintMatcher(index, value, description));
|
||||
return new ContainsHintMatcher(this.name, values);
|
||||
return new ContainsHintMatcher(this.name, values, this.providers);
|
||||
}
|
||||
|
||||
public ContainsHintMatcher withProvider(int index, String provider, Map<String,Object> parameters) {
|
||||
List<ProviderHintMatcher> providers = new ArrayList<ProviderHintMatcher>(this.providers);
|
||||
providers.add(new ProviderHintMatcher(index, provider, parameters));
|
||||
return new ContainsHintMatcher(this.name, this.values, providers);
|
||||
}
|
||||
|
||||
public ContainsHintMatcher withProvider(String provider, String key, Object value) {
|
||||
return withProvider(this.providers.size(), provider, Collections.singletonMap(key, value));
|
||||
}
|
||||
|
||||
public ContainsHintMatcher withProvider(String provider) {
|
||||
return withProvider(this.providers.size(), provider, null);
|
||||
}
|
||||
|
||||
private ItemHint getFirstHintWithName(ConfigurationMetadata metadata, String name) {
|
||||
@@ -314,4 +342,50 @@ public class ConfigurationMetadataMatchers {
|
||||
|
||||
}
|
||||
|
||||
public static class ProviderHintMatcher extends BaseMatcher<ItemHint> {
|
||||
private final int index;
|
||||
private final String name;
|
||||
private final Map<String, Object> parameters;
|
||||
|
||||
public ProviderHintMatcher(int index, String name, Map<String, Object> parameters) {
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
ItemHint hint = (ItemHint) item;
|
||||
if (this.index + 1 > hint.getProviders().size()) {
|
||||
return false;
|
||||
}
|
||||
ItemHint.ProviderHint providerHint = hint.getProviders().get(index);
|
||||
if (this.name != null
|
||||
&& !this.name.equals(providerHint.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (this.parameters != null) {
|
||||
for (Map.Entry<String, Object> entry : this.parameters.entrySet()) {
|
||||
if (!IsMapContaining.hasEntry(entry.getKey(), entry.getValue())
|
||||
.matches(providerHint.getParameters())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("provider hint ");
|
||||
if (this.name != null) {
|
||||
description.appendText(" name ").appendValue(this.name);
|
||||
}
|
||||
if (this.parameters != null) {
|
||||
description.appendText(" parameters ").appendValue(this.parameters);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -56,6 +58,9 @@ public class JsonMarshallerTests {
|
||||
metadata.add(ItemHint.newHint("a.b"));
|
||||
metadata.add(ItemHint.newHint("c", new ItemHint.ValueHint(123, "hey"),
|
||||
new ItemHint.ValueHint(456, null)));
|
||||
metadata.add(new ItemHint("d", null, Arrays.asList(
|
||||
new ItemHint.ProviderHint("first", Collections.<String,Object>singletonMap("target", "foo")),
|
||||
new ItemHint.ProviderHint("second", null))));
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
JsonMarshaller marshaller = new JsonMarshaller();
|
||||
marshaller.write(metadata, outputStream);
|
||||
@@ -76,6 +81,9 @@ public class JsonMarshallerTests {
|
||||
assertThat(read, containsHint("a.b"));
|
||||
assertThat(read,
|
||||
containsHint("c").withValue(0, 123, "hey").withValue(1, 456, null));
|
||||
assertThat(read, containsHint("d")
|
||||
.withProvider("first", "target", "foo")
|
||||
.withProvider("second"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user