Add processor to generate configuration meta-data

Adds an annotation processor to generates a JSON meta-data file at
compile time from @ConfigurationProperties items. Each meta-data file
can include an array or 'properties' and 'groups'.

A 'property' is a single item that may appear in a Spring Boot
'application.properties' file with a given value. For example,
'server.port' and 'server.context-path' are properties. Each property
may optionally include 'type' and 'description' attributes to provide
the data type (e.g. `java.lang.Integer`, `java.lang.String`) and
some short documentation (taken from the field javadoc) about what the
property is for. For consistency, the type of a primitive is translated
to its wrapper counterpart, i.e. `boolean` becomes `java.lang.Boolean`.

A 'group' provides a higher level grouping of properties. For example
the 'server.port' and 'server.context-path' properties are in the
'server' group.

Both 'property' and 'group' items may additional have 'sourceType' and
'sourceMethod' attributes to indicate the source that contributed them.

Users may use `META-INF/additional-spring-configuration-metadata.json`
to manually provide additionally meta-data that is not covered by
@ConfigurationProperties objects. The contents of this file will be
read and merged with harvested items. The complete meta-data file is
finally written to `META-INF/spring-configuration-metadata.json`.

See gh-1001
This commit is contained in:
Stephane Nicoll
2014-10-31 10:18:28 -07:00
committed by Phillip Webb
parent 45b579c439
commit 884c058e57
32 changed files with 2552 additions and 1 deletions

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2012-2014 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
/**
* Annotation {@link Processor} that writes meta-data file for
* {@code @ConfigurationProperties}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
@SupportedAnnotationTypes({ ConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor {
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties";
private ConfigurationMetadata metadata;
private TypeUtils typeUtils;
protected String configurationPropertiesAnnotation() {
return CONFIGURATION_PROPERTIES_ANNOTATION;
}
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
this.metadata = new ConfigurationMetadata();
this.typeUtils = new TypeUtils(env);
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
processElement(element);
}
}
if (roundEnv.processingOver()) {
writeMetaData(this.metadata);
}
return false;
}
private void processElement(Element element) {
AnnotationMirror annotation = getAnnotation(element,
configurationPropertiesAnnotation());
String prefix = getPrefix(annotation);
if (annotation != null) {
if (element instanceof TypeElement) {
processAnnotatedTypeElement(prefix, (TypeElement) element);
}
else if (element instanceof ExecutableElement) {
processExecutableElement(prefix, (ExecutableElement) element);
}
}
}
private void processAnnotatedTypeElement(String prefix, TypeElement element) {
String type = this.typeUtils.getType(element);
this.metadata.add(ItemMetadata.newGroup(prefix, type, type, null));
processTypeElement(prefix, element);
}
private void processExecutableElement(String prefix, ExecutableElement element) {
if (element.getModifiers().contains(Modifier.PUBLIC)
&& (TypeKind.VOID != element.getReturnType().getKind())) {
Element returns = this.processingEnv.getTypeUtils().asElement(
element.getReturnType());
if (returns instanceof TypeElement) {
this.metadata.add(ItemMetadata.newGroup(prefix,
this.typeUtils.getType(returns),
this.typeUtils.getType(element.getEnclosingElement()),
element.toString()));
processTypeElement(prefix, (TypeElement) returns);
}
}
}
private void processTypeElement(String prefix, TypeElement element) {
TypeElementMembers members = new TypeElementMembers(this.processingEnv, element);
processSimpleTypes(prefix, element, members);
processNestedTypes(prefix, element, members);
}
private void processSimpleTypes(String prefix, TypeElement element,
TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) {
String name = entry.getKey();
ExecutableElement getter = entry.getValue();
ExecutableElement setter = members.getPublicSetters().get(name);
VariableElement field = members.getFields().get(name);
if (setter != null
|| this.typeUtils.isCollectionOrMap(getter.getReturnType())) {
String dataType = this.typeUtils.getType(getter.getReturnType());
String sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field);
this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType,
sourceType, null, description));
}
}
}
private void processNestedTypes(String prefix, TypeElement element,
TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) {
ExecutableElement getter = entry.getValue();
Element returnType = this.processingEnv.getTypeUtils().asElement(
getter.getReturnType());
AnnotationMirror annotation = getAnnotation(getter,
configurationPropertiesAnnotation());
if (returnType != null && returnType instanceof TypeElement
&& annotation == null) {
TypeElement returns = (TypeElement) returnType;
if (this.typeUtils.isEnclosedIn(returnType, element)) {
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix,
entry.getKey());
this.metadata.add(ItemMetadata.newGroup(nestedPrefix,
this.typeUtils.getType(returns),
this.typeUtils.getType(element), getter.toString()));
processTypeElement(nestedPrefix, returns);
}
}
}
}
private AnnotationMirror getAnnotation(Element element, String type) {
for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
if (type.equals(annotation.getAnnotationType().toString())) {
return annotation;
}
}
return null;
}
private String getPrefix(AnnotationMirror annotation) {
Map<String, Object> elementValues = getAnnotationElementValues(annotation);
Object prefix = elementValues.get("prefix");
if (prefix != null && !"".equals(prefix)) {
return (String) prefix;
}
Object value = elementValues.get("value");
if (value != null && !"".equals(value)) {
return (String) value;
}
return null;
}
private Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
Map<String, Object> values = new LinkedHashMap<String, Object>();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation
.getElementValues().entrySet()) {
values.put(entry.getKey().getSimpleName().toString(), entry.getValue()
.getValue());
}
return values;
}
protected void writeMetaData(ConfigurationMetadata metadata) {
metadata = mergeManualMetadata(metadata);
try {
FileObject resource = this.processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "",
"META-INF/spring-configuration-metadata.json");
OutputStream outputStream = resource.openOutputStream();
try {
new JsonMarshaller().write(metadata, outputStream);
}
finally {
outputStream.close();
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private ConfigurationMetadata mergeManualMetadata(ConfigurationMetadata metadata) {
try {
FileObject manualMetadata = this.processingEnv.getFiler().getResource(
StandardLocation.CLASS_PATH, "",
"META-INF/additional-spring-configuration-metadata.json");
InputStream inputStream = manualMetadata.openInputStream();
try {
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
try {
merged.addAll(new JsonMarshaller().read(inputStream));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return merged;
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
ex.printStackTrace();
return metadata;
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2012-2014 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;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
/**
* Provides access to relevant {@link TypeElement} members.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
class TypeElementMembers {
private static final String OBJECT_CLASS_NAME = Object.class.getName();
private final ProcessingEnvironment env;
private final Map<String, VariableElement> fields = new LinkedHashMap<String, VariableElement>();
private final Map<String, ExecutableElement> publicGetters = new LinkedHashMap<String, ExecutableElement>();
private final Map<String, ExecutableElement> publicSetters = new LinkedHashMap<String, ExecutableElement>();
public TypeElementMembers(ProcessingEnvironment env, TypeElement element) {
this.env = env;
process(element);
}
private void process(TypeElement element) {
for (ExecutableElement method : ElementFilter.methodsIn(element
.getEnclosedElements())) {
processMethod(method);
}
for (VariableElement field : ElementFilter
.fieldsIn(element.getEnclosedElements())) {
processField(field);
}
Element superType = this.env.getTypeUtils().asElement(element.getSuperclass());
if (superType != null && superType instanceof TypeElement
&& !OBJECT_CLASS_NAME.equals(superType.toString())) {
process((TypeElement) superType);
}
}
private void processMethod(ExecutableElement method) {
if (method.getModifiers().contains(Modifier.PUBLIC)) {
String name = method.getSimpleName().toString();
if (isGetter(method) && !this.publicGetters.containsKey(name)) {
this.publicGetters.put(getAccessorName(name), method);
}
else if (isSetter(method) && !this.publicSetters.containsKey(name)) {
this.publicSetters.put(getAccessorName(name), method);
}
}
}
private boolean isGetter(ExecutableElement method) {
String name = method.getSimpleName().toString();
return (name.startsWith("get") || name.startsWith("is"))
&& method.getParameters().isEmpty()
&& (TypeKind.VOID != method.getReturnType().getKind());
}
private boolean isSetter(ExecutableElement method) {
final String name = method.getSimpleName().toString();
return name.startsWith("set") && method.getParameters().size() == 1
&& (TypeKind.VOID == method.getReturnType().getKind());
}
private String getAccessorName(String methodName) {
String name = methodName.startsWith("is") ? methodName.substring(2) : methodName
.substring(3);
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
return name;
}
private void processField(VariableElement field) {
String name = field.getSimpleName().toString();
if (!this.fields.containsKey(name)) {
this.fields.put(name, field);
}
}
public Map<String, VariableElement> getFields() {
return Collections.unmodifiableMap(this.fields);
}
public Map<String, ExecutableElement> getPublicGetters() {
return Collections.unmodifiableMap(this.publicGetters);
}
public Map<String, ExecutableElement> getPublicSetters() {
return Collections.unmodifiableMap(this.publicSetters);
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2014 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;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Types;
/**
* Type Utilities.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
class TypeUtils {
private static final Map<TypeKind, Class<?>> PRIMITIVE_WRAPPERS;
static {
Map<TypeKind, Class<?>> wrappers = new HashMap<TypeKind, Class<?>>();
wrappers.put(TypeKind.BOOLEAN, Boolean.class);
wrappers.put(TypeKind.BYTE, Byte.class);
wrappers.put(TypeKind.CHAR, Character.class);
wrappers.put(TypeKind.DOUBLE, Double.class);
wrappers.put(TypeKind.FLOAT, Float.class);
wrappers.put(TypeKind.INT, Integer.class);
wrappers.put(TypeKind.LONG, Long.class);
wrappers.put(TypeKind.SHORT, Short.class);
PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(wrappers);
}
private final ProcessingEnvironment env;
private final TypeMirror collectionType;
private final TypeMirror mapType;
public TypeUtils(ProcessingEnvironment env) {
this.env = env;
Types types = env.getTypeUtils();
WildcardType wc = types.getWildcardType(null, null);
this.collectionType = types.getDeclaredType(this.env.getElementUtils()
.getTypeElement(Collection.class.getName()), wc);
this.mapType = types.getDeclaredType(
this.env.getElementUtils().getTypeElement(Map.class.getName()), wc, wc);
}
public String getType(Element element) {
return getType(element == null ? null : element.asType());
}
public String getType(TypeMirror type) {
if (type == null) {
return null;
}
Class<?> wrapper = PRIMITIVE_WRAPPERS.get(type.getKind());
if (wrapper != null) {
return wrapper.getName();
}
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return getType(enclosingElement) + "$"
+ declaredType.asElement().getSimpleName().toString();
}
}
return type.toString();
}
public boolean isCollectionOrMap(TypeMirror type) {
return this.env.getTypeUtils().isAssignable(type, this.collectionType)
|| this.env.getTypeUtils().isAssignable(type, this.mapType);
}
public boolean isEnclosedIn(Element candidate, TypeElement element) {
if (candidate == null || element == null) {
return false;
}
if (candidate.equals(element)) {
return true;
}
return isEnclosedIn(candidate.getEnclosingElement(), element);
}
public String getJavaDoc(Element element) {
String javadoc = (element == null ? null : this.env.getElementUtils()
.getDocComment(element));
if (javadoc != null) {
javadoc = javadoc.trim();
}
return ("".equals(javadoc) ? null : javadoc);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012-2014 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.Collections;
import java.util.List;
/**
* Configuration meta-data.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
* @see ItemMetadata
*/
public class ConfigurationMetadata {
private final List<ItemMetadata> items;
public ConfigurationMetadata() {
this.items = new ArrayList<ItemMetadata>();
}
public ConfigurationMetadata(ConfigurationMetadata metadata) {
this.items = new ArrayList<ItemMetadata>(metadata.getItems());
}
/**
* Add item meta-data.
* @param itemMetadata the meta-data to add
*/
public void add(ItemMetadata itemMetadata) {
this.items.add(itemMetadata);
Collections.sort(this.items);
}
/**
* Add all properties from another {@link ConfigurationMetadata}.
* @param metadata the {@link ConfigurationMetadata} instance to merge
*/
public void addAll(ConfigurationMetadata metadata) {
this.items.addAll(metadata.getItems());
Collections.sort(this.items);
}
/**
* @return the meta-data properties.
*/
public List<ItemMetadata> getItems() {
return Collections.unmodifiableList(this.items);
}
public static String nestedPrefix(String prefix, String name) {
String nestedPrefix = (prefix == null ? "" : prefix);
String dashedName = toDashedCase(name);
nestedPrefix += ("".equals(nestedPrefix) ? dashedName : "." + dashedName);
return nestedPrefix;
}
static String toDashedCase(String name) {
StringBuilder dashed = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (Character.isUpperCase(c) && dashed.length() > 0) {
dashed.append("-");
}
dashed.append(Character.toLowerCase(c));
}
return dashed.toString();
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2014 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;
/**
* A group or property meta-data item from some {@link ConfigurationMetadata}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
* @see ConfigurationMetadata
*/
public class ItemMetadata implements Comparable<ItemMetadata> {
private final ItemType itemType;
private final String name;
private final String type;
private final String description;
private final String sourceType;
private String sourceMethod;
ItemMetadata(ItemType itemType, String prefix, String name, String type,
String sourceType, String sourceMethod, String description) {
super();
this.itemType = itemType;
this.name = buildName(prefix, name);
this.type = type;
this.sourceType = sourceType;
this.sourceMethod = sourceMethod;
this.description = description;
}
private String buildName(String prefix, String name) {
while (prefix != null && prefix.endsWith(".")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
StringBuilder fullName = new StringBuilder(prefix == null ? "" : prefix);
if (fullName.length() > 0 && name != null) {
fullName.append(".");
}
fullName.append(name == null ? "" : ConfigurationMetadata.toDashedCase(name));
return fullName.toString();
}
public boolean isOfItemType(ItemType itemType) {
return this.itemType == itemType;
}
public String getName() {
return this.name;
}
public String getType() {
return this.type;
}
public String getSourceType() {
return this.sourceType;
}
public String getSourceMethod() {
return this.sourceMethod;
}
public String getDescription() {
return this.description;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder(this.name);
buildToStringProperty(string, "type", this.type);
buildToStringProperty(string, "sourceType", this.sourceType);
buildToStringProperty(string, "description", this.description);
return string.toString();
}
protected final void buildToStringProperty(StringBuilder string, String property,
Object value) {
if (value != null) {
string.append(" ").append(property).append(":").append(value);
}
}
@Override
public int compareTo(ItemMetadata o) {
return getName().compareTo(o.getName());
}
public static ItemMetadata newGroup(String name, String type, String sourceType,
String sourceMethod) {
return new ItemMetadata(ItemType.GROUP, name, null, type, sourceType,
sourceMethod, null);
}
public static ItemMetadata newProperty(String prefix, String name, String type,
String sourceType, String sourceMethod, String description) {
return new ItemMetadata(ItemType.PROPERTY, prefix, name, type, sourceType,
sourceMethod, description);
}
/**
* The item type.
*/
public static enum ItemType {
GROUP, PROPERTY
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2012-2014 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.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.LinkedHashSet;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
/**
* Marshaller to write {@link ConfigurationMetadata} as JSON.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
public class JsonMarshaller {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final int BUFFER_SIZE = 4098;
public void write(ConfigurationMetadata metadata, OutputStream outputStream)
throws IOException {
JSONObject object = new JSONObject();
object.put("groups", toJsonArray(metadata, ItemType.GROUP));
object.put("properties", toJsonArray(metadata, ItemType.PROPERTY));
outputStream.write(object.toString(2).getBytes(UTF_8));
}
private JSONArray toJsonArray(ConfigurationMetadata metadata, ItemType itemType) {
JSONArray jsonArray = new JSONArray();
for (ItemMetadata item : metadata.getItems()) {
if (item.isOfItemType(itemType)) {
jsonArray.put(toJsonObject(item));
}
}
return jsonArray;
}
private JSONObject toJsonObject(ItemMetadata item) {
JSONObject jsonObject = new JSONOrderedObject();
jsonObject.put("name", item.getName());
putIfPresent(jsonObject, "type", item.getType());
putIfPresent(jsonObject, "description", item.getDescription());
putIfPresent(jsonObject, "sourceType", item.getSourceType());
putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod());
return jsonObject;
}
private void putIfPresent(JSONObject jsonObject, String name, Object value) {
if (value != null) {
jsonObject.put(name, value);
}
}
public ConfigurationMetadata read(InputStream inputStream) throws IOException {
ConfigurationMetadata metadata = new ConfigurationMetadata();
JSONObject object = new JSONObject(toString(inputStream));
JSONArray groups = object.optJSONArray("groups");
if (groups != null) {
for (int i = 0; i < groups.length(); i++) {
metadata.add(toItemMetadata((JSONObject) groups.get(i), ItemType.GROUP));
}
}
JSONArray properties = object.optJSONArray("properties");
if (properties != null) {
for (int i = 0; i < properties.length(); i++) {
metadata.add(toItemMetadata((JSONObject) properties.get(i),
ItemType.PROPERTY));
}
}
return metadata;
}
private ItemMetadata toItemMetadata(JSONObject object, ItemType itemType) {
String name = object.getString("name");
String type = object.optString("type", null);
String description = object.optString("description", null);
String sourceType = object.optString("sourceType", null);
String sourceMethod = object.optString("sourceMethod", null);
return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod,
description);
}
private String toString(InputStream inputStream) throws IOException {
StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(inputStream, UTF_8);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = reader.read(buffer)) != -1) {
out.append(buffer, 0, bytesRead);
}
return out.toString();
}
/**
* Extension to {@link JSONObject} that remembers the order of inserts.
*/
@SuppressWarnings("rawtypes")
private static class JSONOrderedObject extends JSONObject {
private Set<String> keys = new LinkedHashSet<String>();
@Override
public JSONObject put(String key, Object value) throws JSONException {
this.keys.add(key);
return super.put(key, value);
}
@Override
public Set keySet() {
return this.keys;
}
}
}

View File

@@ -0,0 +1 @@
org.springframework.boot.configurationprocessor.ConfigurationMetadataAnnotationProcessor