mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-05-02 11:25:23 +01:00
Add support for property hint
Create a new section in the meta-data called "hints" where users can
provide hints about a given property. The most basic use case for now
is to provide a list of values that a property can have. Each value may
have a description.
This sample JSON provides a basic example for a property called `foo.mode`
that exposes 3 values: "auto", "basic" and "advanced".
```
"hints": [
{
"id": "foo.mode",
"values": [
{
"value": "auto",
"description": "Some smart description."
},
{
"name": "basic"
},
{
"name": "advanced"
}
]
}
]
```
This information can be read by tools (such as IDE) and offer an
auto-completion with the list of values.
Closes gh-2054
This commit is contained in:
@@ -7,7 +7,9 @@ contextual help and "`code completion`" as users are working with `application.p
|
||||
or `application.yml` files.
|
||||
|
||||
The majority of the meta-data file is generated automatically at compile time by
|
||||
processing all items annotated with `@ConfigurationProperties`.
|
||||
processing all items annotated with `@ConfigurationProperties`. However, it is possible
|
||||
to <<configuration-metadata-additional-metadata,write part of the meta-data manually>>
|
||||
for corner cases or more advanced use cases.
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +17,8 @@ processing all items annotated with `@ConfigurationProperties`.
|
||||
=== Meta-data format
|
||||
Configuration meta-data files are located inside jars under
|
||||
`META-INF/spring-configuration-metadata.json` They use a simple JSON format with items
|
||||
categorized under either "`groups`" or "`properties`":
|
||||
categorized under either "`groups`" or "`properties`" and additional values hint
|
||||
categorized under "hints":
|
||||
|
||||
[source,json,indent=0]
|
||||
----
|
||||
@@ -24,6 +27,12 @@ categorized under either "`groups`" or "`properties`":
|
||||
"name": "server",
|
||||
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
|
||||
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
|
||||
},
|
||||
{
|
||||
"name": "server.tomcat",
|
||||
"type": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat",
|
||||
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
|
||||
"sourceMethod": "getTomcat()"
|
||||
}
|
||||
...
|
||||
],"properties": [
|
||||
@@ -37,8 +46,33 @@ categorized under either "`groups`" or "`properties`":
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
|
||||
"defaultValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "server.tomcat.compression",
|
||||
"type": "java.lang.String",
|
||||
"description": "Controls response compression.",
|
||||
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat",
|
||||
"defaultValue": "off"
|
||||
}
|
||||
...
|
||||
],"hints": [
|
||||
{
|
||||
"name": "server.tomcat.compression",
|
||||
"values": [
|
||||
{
|
||||
"value": "off",
|
||||
"description": "Disable compression."
|
||||
},
|
||||
{
|
||||
"value": "on",
|
||||
"description": "Enable compression of responses over 2048 byte."
|
||||
},
|
||||
{
|
||||
"value": "force",
|
||||
"description": "Enable compression of all responses."
|
||||
},
|
||||
]
|
||||
}
|
||||
]}
|
||||
----
|
||||
|
||||
@@ -59,6 +93,9 @@ provide a contextual grouping for properties. For example the `server.port` and
|
||||
NOTE: It is not required that every "`property`" has a "`group`", some properties might
|
||||
just exist in their own right.
|
||||
|
||||
Finally, "`hints`" are additional information used to assist the user in configuring a
|
||||
given property. When configuring the `server.tomcat.compression` property, a tool can
|
||||
use it to offer some auto-completion help for the `off`, `on` and `force` values.
|
||||
|
||||
|
||||
[[configuration-metadata-group-attributes]]
|
||||
@@ -152,6 +189,46 @@ The JSON object contained in the `properties` array can contain the following at
|
||||
|===
|
||||
|
||||
|
||||
[[configuration-metadata-hints-attributes]]
|
||||
==== Hint Attributes
|
||||
The JSON object contained in the `hints` array can contain the following attributes:
|
||||
|
||||
[cols="1,1,4"]
|
||||
|===
|
||||
|Name | Type |Purpose
|
||||
|
||||
|`name`
|
||||
| String
|
||||
| The full name of the property that this hint refers to. Names are in lowercase dashed
|
||||
form (e.g. `server.servlet-path`). If the property refers to a map (e.g.
|
||||
`system.contexts`) the hint either applies to the _keys_ of the map (`system.context.keys`)
|
||||
or the values (`system.context.values`). This attribute is mandatory.
|
||||
|
||||
|`values`
|
||||
| ValueHint[]
|
||||
| A list of valid values as defined by the `ValueHint` object (see below). Each entry defines
|
||||
the value and may have a description
|
||||
|===
|
||||
|
||||
The JSON object contained in the `values` array of each `hint` element can contain the
|
||||
following attributes:
|
||||
|
||||
[cols="1,1,4"]
|
||||
|===
|
||||
|Name | Type |Purpose
|
||||
|
||||
|`value`
|
||||
| Object
|
||||
| A valid value for the element to which the hint refers to. Can also be an array of value(s)
|
||||
if the type of the property is an array. This attribute is mandatory.
|
||||
|
||||
|`description`
|
||||
| String
|
||||
| A short description of the value that can be displayed to users. May be omitted if no
|
||||
description is available. It is recommended that descriptions are a short paragraphs,
|
||||
with the first line providing a concise summary. The last line in the description should
|
||||
end with a period (`.`).
|
||||
|===
|
||||
|
||||
[[configuration-metadata-repeated-items]]
|
||||
==== Repeated meta-data items
|
||||
@@ -161,7 +238,19 @@ appear multiple times within a meta-data file. For example, Spring Boot binds
|
||||
offering overlap of property names. Consumers of meta-data should take care to ensure
|
||||
that they support such scenarios.
|
||||
|
||||
=== Providing manual hints
|
||||
|
||||
To improve the user experience and further assist the user in configuring a given
|
||||
property, you can provide additional meta-data that describes the list of potential
|
||||
values for a property.
|
||||
|
||||
The `name` attribute of each hint refers to the `name` of a property. In the initial
|
||||
example above, we provide 3 values for the `server.tomcat.compression` property: `on`,
|
||||
`off` and `force`.
|
||||
|
||||
If your property is of type `Map`, you can provide hints for both the keys and the
|
||||
values (but not for the map itself). The special `.keys` and `.values` suffixes must
|
||||
be used to refer to the keys and the values respectively.
|
||||
|
||||
[[configuration-metadata-annotation-processor]]
|
||||
=== Generating your own meta-data using the annotation processor
|
||||
@@ -250,8 +339,9 @@ if it were nested.
|
||||
==== Adding additional meta-data
|
||||
Spring Boot's configuration file handling is quite flexible; and it often the case that
|
||||
properties may exist that are not bound to a `@ConfigurationProperties` bean. To support
|
||||
such cases, the annotation processor will automatically merge items from
|
||||
`META-INF/additional-spring-configuration-metadata.json` into the main meta-data file.
|
||||
such cases and allow you to provide custom "hints", the annotation processor will
|
||||
automatically merge items from `META-INF/additional-spring-configuration-metadata.json`
|
||||
into the main meta-data file.
|
||||
|
||||
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
|
||||
|
||||
+19
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 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.
|
||||
@@ -36,12 +36,16 @@ public class ConfigurationMetadata {
|
||||
|
||||
private final List<ItemMetadata> items;
|
||||
|
||||
private final List<ItemHint> hints;
|
||||
|
||||
public ConfigurationMetadata() {
|
||||
this.items = new ArrayList<ItemMetadata>();
|
||||
this.hints = new ArrayList<ItemHint>();
|
||||
}
|
||||
|
||||
public ConfigurationMetadata(ConfigurationMetadata metadata) {
|
||||
this.items = new ArrayList<ItemMetadata>(metadata.getItems());
|
||||
this.hints = new ArrayList<ItemHint>(metadata.getHints());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +57,11 @@ public class ConfigurationMetadata {
|
||||
Collections.sort(this.items);
|
||||
}
|
||||
|
||||
public void add(ItemHint itemHint) {
|
||||
this.hints.add(itemHint);
|
||||
Collections.sort(this.hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all properties from another {@link ConfigurationMetadata}.
|
||||
* @param metadata the {@link ConfigurationMetadata} instance to merge
|
||||
@@ -60,6 +69,8 @@ public class ConfigurationMetadata {
|
||||
public void addAll(ConfigurationMetadata metadata) {
|
||||
this.items.addAll(metadata.getItems());
|
||||
Collections.sort(this.items);
|
||||
this.hints.addAll(metadata.getHints());
|
||||
Collections.sort(this.hints);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +80,13 @@ public class ConfigurationMetadata {
|
||||
return Collections.unmodifiableList(this.items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the meta-data hints.
|
||||
*/
|
||||
public List<ItemHint> getHints() {
|
||||
return Collections.unmodifiableList(this.hints);
|
||||
}
|
||||
|
||||
public static String nestedPrefix(String prefix, String name) {
|
||||
String nestedPrefix = (prefix == null ? "" : prefix);
|
||||
String dashedName = toDashedCase(name);
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provide hints on an {@link ItemMetadata}. Defines the list of possible values for
|
||||
* a particular item as {@link ItemHint.ValueHint} instances.
|
||||
* <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 hint for a map using its property name is therefore invalid.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class ItemHint implements Comparable<ItemHint> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<ValueHint> values;
|
||||
|
||||
public ItemHint(String name, List<ValueHint> values) {
|
||||
this.name = toCanonicalName(name);
|
||||
this.values = new ArrayList<ValueHint>(values);
|
||||
}
|
||||
|
||||
private String toCanonicalName(String name) {
|
||||
int dot = name.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
String prefix = name.substring(0, dot);
|
||||
String originalName = name.substring(dot, name.length());
|
||||
return prefix + ConfigurationMetadata.toDashedCase(originalName);
|
||||
}
|
||||
return ConfigurationMetadata.toDashedCase(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public List<ValueHint> getValues() {
|
||||
return Collections.unmodifiableList(this.values);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ItemHint{" + "name='" + this.name + '\'' +
|
||||
", values=" + this.values +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
public static class ValueHint {
|
||||
private final Object value;
|
||||
|
||||
private final String description;
|
||||
|
||||
public ValueHint(Object value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValueHint{" + "value=" + this.value +
|
||||
", description='" + this.description + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 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,7 +22,10 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.JSONArray;
|
||||
@@ -48,6 +51,7 @@ public class JsonMarshaller {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("groups", toJsonArray(metadata, ItemType.GROUP));
|
||||
object.put("properties", toJsonArray(metadata, ItemType.PROPERTY));
|
||||
object.put("hints", toJsonArray(metadata.getHints()));
|
||||
outputStream.write(object.toString(2).getBytes(UTF_8));
|
||||
}
|
||||
|
||||
@@ -61,6 +65,14 @@ public class JsonMarshaller {
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
private JSONArray toJsonArray(Collection<ItemHint> hints) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for (ItemHint hint : hints) {
|
||||
jsonArray.put(toJsonObject(hint));
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
private JSONObject toJsonObject(ItemMetadata item) {
|
||||
JSONObject jsonObject = new JSONOrderedObject();
|
||||
jsonObject.put("name", item.getName());
|
||||
@@ -78,13 +90,40 @@ public class JsonMarshaller {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private JSONObject toJsonObject(ItemHint hint) {
|
||||
JSONObject jsonObject = new JSONOrderedObject();
|
||||
jsonObject.put("name", hint.getName());
|
||||
if (!hint.getValues().isEmpty()) {
|
||||
JSONArray valuesArray = new JSONArray();
|
||||
for (ItemHint.ValueHint valueHint : hint.getValues()) {
|
||||
JSONObject valueObject = new JSONOrderedObject();
|
||||
putHintValue(valueObject, valueHint.getValue());
|
||||
putIfPresent(valueObject, "description", valueHint.getDescription());
|
||||
valuesArray.put(valueObject);
|
||||
}
|
||||
jsonObject.put("values", valuesArray);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
private void putIfPresent(JSONObject jsonObject, String name, Object value) {
|
||||
if (value != null) {
|
||||
jsonObject.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void putHintValue(JSONObject jsonObject, Object value) {
|
||||
Object hintValue = extractItemValue(value);
|
||||
jsonObject.put("value", hintValue);
|
||||
}
|
||||
|
||||
private void putDefaultValue(JSONObject jsonObject, Object value) {
|
||||
Object defaultValue = extractItemValue(value);
|
||||
jsonObject.put("defaultValue", defaultValue);
|
||||
}
|
||||
|
||||
private Object extractItemValue(Object value) {
|
||||
Object defaultValue = value;
|
||||
if (value.getClass().isArray()) {
|
||||
JSONArray array = new JSONArray();
|
||||
@@ -95,7 +134,7 @@ public class JsonMarshaller {
|
||||
defaultValue = array;
|
||||
|
||||
}
|
||||
jsonObject.put("defaultValue", defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public ConfigurationMetadata read(InputStream inputStream) throws IOException {
|
||||
@@ -114,6 +153,12 @@ public class JsonMarshaller {
|
||||
ItemType.PROPERTY));
|
||||
}
|
||||
}
|
||||
JSONArray hints = object.optJSONArray("hints");
|
||||
if (hints != null) {
|
||||
for (int i = 0; i < hints.length(); i++) {
|
||||
metadata.add(toItemHint((JSONObject) hints.get(i)));
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -123,23 +168,41 @@ public class JsonMarshaller {
|
||||
String description = object.optString("description", null);
|
||||
String sourceType = object.optString("sourceType", null);
|
||||
String sourceMethod = object.optString("sourceMethod", null);
|
||||
Object defaultValue = readDefaultValue(object);
|
||||
Object defaultValue = readItemValue(object.opt("defaultValue"));
|
||||
boolean deprecated = object.optBoolean("deprecated");
|
||||
return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod,
|
||||
description, defaultValue, deprecated);
|
||||
}
|
||||
|
||||
private Object readDefaultValue(JSONObject object) {
|
||||
Object defaultValue = object.opt("defaultValue");
|
||||
if (defaultValue instanceof JSONArray) {
|
||||
JSONArray array = (JSONArray) defaultValue;
|
||||
private ItemHint toItemHint(JSONObject object) {
|
||||
String name = object.getString("name");
|
||||
List<ItemHint.ValueHint> values = new ArrayList<ItemHint.ValueHint>();
|
||||
if (object.has("values")) {
|
||||
JSONArray valuesArray = object.getJSONArray("values");
|
||||
for (int i = 0; i < valuesArray.length(); i++) {
|
||||
values.add(toValueHint((JSONObject) valuesArray.get(i)));
|
||||
}
|
||||
}
|
||||
return new ItemHint(name, values);
|
||||
}
|
||||
|
||||
private ItemHint.ValueHint toValueHint(JSONObject object) {
|
||||
Object value = readItemValue(object.get("value"));
|
||||
String description = object.optString("description", null);
|
||||
return new ItemHint.ValueHint(value, description);
|
||||
}
|
||||
|
||||
|
||||
private Object readItemValue(Object value) {
|
||||
if (value instanceof JSONArray) {
|
||||
JSONArray array = (JSONArray) value;
|
||||
Object[] content = new Object[array.length()];
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
content[i] = array.get(i);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
private String toString(InputStream inputStream) throws IOException {
|
||||
|
||||
+71
-9
@@ -27,6 +27,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationsample.incremental.BarProperties;
|
||||
import org.springframework.boot.configurationsample.incremental.FooProperties;
|
||||
import org.springframework.boot.configurationsample.incremental.RenamedBarProperties;
|
||||
@@ -58,6 +59,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsHint;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
|
||||
import static org.springframework.boot.configurationprocessor.MetadataStore.METADATA_PATH;
|
||||
|
||||
@@ -324,12 +326,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergingOfAdditionalMetadata() throws Exception {
|
||||
File metaInfFolder = new File(this.compiler.getOutputLocation(), "META-INF");
|
||||
metaInfFolder.mkdirs();
|
||||
File additionalMetadataFile = new File(metaInfFolder,
|
||||
"additional-spring-configuration-metadata.json");
|
||||
additionalMetadataFile.createNewFile();
|
||||
public void mergingOfAdditionalProperty() throws Exception {
|
||||
File additionalMetadataFile = createAdditionalMetadataFile();
|
||||
|
||||
JSONObject property = new JSONObject();
|
||||
property.put("name", "foo");
|
||||
@@ -339,10 +337,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
|
||||
properties.put(property);
|
||||
JSONObject additionalMetadata = new JSONObject();
|
||||
additionalMetadata.put("properties", properties);
|
||||
FileWriter writer = new FileWriter(additionalMetadataFile);
|
||||
additionalMetadata.write(writer);
|
||||
writer.flush();
|
||||
|
||||
writeMetadata(additionalMetadataFile, additionalMetadata);
|
||||
ConfigurationMetadata metadata = compile(SimpleProperties.class);
|
||||
|
||||
assertThat(metadata, containsProperty("simple.comparator"));
|
||||
@@ -352,6 +348,28 @@ public class ConfigurationMetadataAnnotationProcessorTests {
|
||||
.fromSource(AdditionalMetadata.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergingOfSimpleHint() throws Exception {
|
||||
writeAdditionalHints(
|
||||
ItemHint.newHint("simple.the-name", new ItemHint.ValueHint("boot", "Bla bla"),
|
||||
new ItemHint.ValueHint("spring", null)));
|
||||
|
||||
ConfigurationMetadata metadata = compile(SimpleProperties.class);
|
||||
assertThat(metadata, containsHint("simple.the-name")
|
||||
.withValue(0, "boot", "Bla bla")
|
||||
.withValue(1, "spring", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergingOfHintWithNonCanonicalName() throws Exception {
|
||||
writeAdditionalHints(
|
||||
ItemHint.newHint("simple.theName", new ItemHint.ValueHint("boot", "Bla bla")));
|
||||
|
||||
ConfigurationMetadata metadata = compile(SimpleProperties.class);
|
||||
assertThat(metadata, containsHint("simple.the-name")
|
||||
.withValue(0, "boot", "Bla bla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incrementalBuild() throws Exception {
|
||||
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
|
||||
@@ -448,6 +466,50 @@ public class ConfigurationMetadataAnnotationProcessorTests {
|
||||
return processor.getMetadata();
|
||||
}
|
||||
|
||||
private void writeAdditionalHints(ItemHint... hints) throws IOException {
|
||||
File additionalMetadataFile = createAdditionalMetadataFile();
|
||||
|
||||
JSONArray hintsArray = new JSONArray();
|
||||
for (ItemHint hint : hints) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("name", hint.getName());
|
||||
JSONArray valuesArray = new JSONArray();
|
||||
for (ItemHint.ValueHint valueHint : hint.getValues()) {
|
||||
JSONObject valueJsonObject = new JSONObject();
|
||||
valueJsonObject.put("value", valueHint.getValue());
|
||||
String description = valueHint.getDescription();
|
||||
if (description != null) {
|
||||
valueJsonObject.put("description", description);
|
||||
}
|
||||
valuesArray.put(valueJsonObject);
|
||||
}
|
||||
jsonObject.put("values", valuesArray);
|
||||
hintsArray.put(jsonObject);
|
||||
}
|
||||
JSONObject additionalMetadata = new JSONObject();
|
||||
additionalMetadata.put("hints", hints);
|
||||
writeMetadata(additionalMetadataFile, additionalMetadata);
|
||||
}
|
||||
|
||||
private File createAdditionalMetadataFile() throws IOException {
|
||||
File metaInfFolder = new File(this.compiler.getOutputLocation(), "META-INF");
|
||||
metaInfFolder.mkdirs();
|
||||
File additionalMetadataFile = new File(metaInfFolder,
|
||||
"additional-spring-configuration-metadata.json");
|
||||
additionalMetadataFile.createNewFile();
|
||||
return additionalMetadataFile;
|
||||
}
|
||||
|
||||
private void writeMetadata(File metadataFile, JSONObject metadata) throws IOException {
|
||||
FileWriter writer = new FileWriter(metadataFile);
|
||||
try {
|
||||
metadata.write(writer);
|
||||
}
|
||||
finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AdditionalMetadata {
|
||||
|
||||
}
|
||||
|
||||
+135
-3
@@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
|
||||
|
||||
@@ -27,6 +31,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.Ite
|
||||
* Hamcrest {@link Matcher} to help test {@link ConfigurationMetadata}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ConfigurationMetadataMatchers {
|
||||
|
||||
@@ -54,6 +59,10 @@ public class ConfigurationMetadataMatchers {
|
||||
return new ContainsItemMatcher(ItemType.PROPERTY, name).ofType(type);
|
||||
}
|
||||
|
||||
public static ContainsHintMatcher containsHint(String name) {
|
||||
return new ContainsHintMatcher(name);
|
||||
}
|
||||
|
||||
public static class ContainsItemMatcher extends BaseMatcher<ConfigurationMetadata> {
|
||||
|
||||
private final ItemType itemType;
|
||||
@@ -89,7 +98,7 @@ public class ConfigurationMetadataMatchers {
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
|
||||
ItemMetadata itemMetadata = getFirstPropertyWithName(metadata, this.name);
|
||||
ItemMetadata itemMetadata = getFirstItemWithName(metadata, this.name);
|
||||
if (itemMetadata == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -117,7 +126,7 @@ public class ConfigurationMetadataMatchers {
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
|
||||
ItemMetadata property = getFirstPropertyWithName(metadata, this.name);
|
||||
ItemMetadata property = getFirstItemWithName(metadata, this.name);
|
||||
if (property == null) {
|
||||
description.appendText("missing "
|
||||
+ this.itemType.toString().toLowerCase() + " " + this.name);
|
||||
@@ -179,7 +188,7 @@ public class ConfigurationMetadataMatchers {
|
||||
this.sourceType, this.description, this.defaultValue, true);
|
||||
}
|
||||
|
||||
private ItemMetadata getFirstPropertyWithName(ConfigurationMetadata metadata,
|
||||
private ItemMetadata getFirstItemWithName(ConfigurationMetadata metadata,
|
||||
String name) {
|
||||
for (ItemMetadata item : metadata.getItems()) {
|
||||
if (item.isOfItemType(this.itemType) && name.equals(item.getName())) {
|
||||
@@ -191,4 +200,127 @@ public class ConfigurationMetadataMatchers {
|
||||
|
||||
}
|
||||
|
||||
public static class ContainsHintMatcher extends BaseMatcher<ConfigurationMetadata> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<ValueHintMatcher> values;
|
||||
|
||||
public ContainsHintMatcher(String name) {
|
||||
this(name, new ArrayList<ValueHintMatcher>());
|
||||
}
|
||||
|
||||
public ContainsHintMatcher(String name, List<ValueHintMatcher> values) {
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
|
||||
ItemHint itemHint = getFirstHintWithName(metadata, this.name);
|
||||
if (itemHint == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.name != null && !this.name.equals(itemHint.getName())) {
|
||||
return false;
|
||||
}
|
||||
for (ValueHintMatcher value : this.values) {
|
||||
if (!value.matches(itemHint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
|
||||
ItemHint itemHint = getFirstHintWithName(metadata, this.name);
|
||||
if (itemHint == null) {
|
||||
description.appendText("missing hint " + this.name);
|
||||
}
|
||||
else {
|
||||
description.appendText(
|
||||
"was hint ").appendValue(itemHint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("hints for " + this.name);
|
||||
if (this.values != null) {
|
||||
description.appendText(" values ").appendValue(this.values);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private ItemHint getFirstHintWithName(ConfigurationMetadata metadata,
|
||||
String name) {
|
||||
for (ItemHint hint : metadata.getHints()) {
|
||||
if (name.equals(hint.getName())) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ValueHintMatcher extends BaseMatcher<ItemHint> {
|
||||
private final int index;
|
||||
private final Object value;
|
||||
private final String description;
|
||||
|
||||
public ValueHintMatcher(int index, Object value, String description) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
ItemHint hint = (ItemHint) item;
|
||||
if (this.index + 1 > hint.getValues().size()) {
|
||||
return false;
|
||||
}
|
||||
ItemHint.ValueHint valueHint = hint.getValues().get(this.index);
|
||||
if (this.value != null
|
||||
&& !this.value.equals(valueHint.getValue())) {
|
||||
return false;
|
||||
}
|
||||
if (this.description != null
|
||||
&& !this.description.equals(valueHint.getDescription())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("value hint at index '"+this.index+"'");
|
||||
if (this.value != null) {
|
||||
description.appendText(" value ").appendValue(this.value);
|
||||
}
|
||||
if (this.description != null) {
|
||||
description.appendText(" description ").appendValue(this.description);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemHint.ValueHint getValueHint(ItemHint hint) {
|
||||
for (ItemHint.ValueHint valueHint : hint.getValues()) {
|
||||
if (this.value.equals(valueHint.getValue())) {
|
||||
return valueHint;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 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.
|
||||
@@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsHint;
|
||||
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
|
||||
|
||||
/**
|
||||
@@ -52,6 +53,9 @@ public class JsonMarshallerTests {
|
||||
metadata.add(ItemMetadata.newProperty("f", null, null, null, null, null,
|
||||
new Boolean[] { true, false }, false));
|
||||
metadata.add(ItemMetadata.newGroup("d", null, null, null));
|
||||
metadata.add(ItemHint.newHint("a.b"));
|
||||
metadata.add(ItemHint.newHint("c", new ItemHint.ValueHint(123, "hey"),
|
||||
new ItemHint.ValueHint(456, null)));
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
JsonMarshaller marshaller = new JsonMarshaller();
|
||||
marshaller.write(metadata, outputStream);
|
||||
@@ -69,6 +73,8 @@ public class JsonMarshallerTests {
|
||||
assertThat(read,
|
||||
containsProperty("f").withDefaultValue(is(new boolean[] { true, false })));
|
||||
assertThat(read, containsGroup("d"));
|
||||
assertThat(read, containsHint("a.b"));
|
||||
assertThat(read, containsHint("c").withValue(0, 123, "hey").withValue(1, 456, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user