diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 2a6d74733f..4154b7e1e8 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -159,6 +159,11 @@ spring-boot-autoconfigure 1.2.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-configuration-metadata + 1.2.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-boot-tools/pom.xml b/spring-boot-tools/pom.xml index 6dfefa64f2..b1bf4dcd71 100644 --- a/spring-boot-tools/pom.xml +++ b/spring-boot-tools/pom.xml @@ -20,6 +20,7 @@ ${basedir}/.. + spring-boot-configuration-metadata spring-boot-configuration-processor spring-boot-dependency-tools spring-boot-loader diff --git a/spring-boot-tools/spring-boot-configuration-metadata/pom.xml b/spring-boot-tools/spring-boot-configuration-metadata/pom.xml new file mode 100644 index 0000000000..115ee41505 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-tools + 1.2.0.BUILD-SNAPSHOT + + spring-boot-configuration-metadata + Spring Boot Configuration Metadata + Spring Boot Configuration Metadata + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.json + json + + + + org.springframework + spring-core + test + + + diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataGroup.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataGroup.java new file mode 100644 index 0000000000..8bb0c8266a --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataGroup.java @@ -0,0 +1,68 @@ +/* + * 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.configurationmetadata; + +import java.util.HashMap; +import java.util.Map; + +/** + * Gather a collection of {@link ConfigurationMetadataProperty} that are sharing + * a common prefix. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class ConfigurationMetadataGroup { + + private final String id; + + private final Map sources = + new HashMap(); + + private final Map properties = + new HashMap(); + + public ConfigurationMetadataGroup(String id) { + this.id = id; + } + + /** + * Return the id of the group. + */ + public String getId() { + return this.id; + } + + /** + * Return the {@link ConfigurationMetadataSource sources} defining + * the properties of this group. + */ + public Map getSources() { + return this.sources; + } + + /** + * Return the {@link ConfigurationMetadataProperty properties} defined in this group. + *

A property may appear more than once for a given source, potentially with conflicting + * type or documentation. This is a "merged" view of the properties of this group. + * @see ConfigurationMetadataSource#getProperties() + */ + public Map getProperties() { + return this.properties; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataItem.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataItem.java new file mode 100644 index 0000000000..4487ee9702 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataItem.java @@ -0,0 +1,58 @@ +/* + * 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.configurationmetadata; + +/** + * An extension of {@link ConfigurationMetadataProperty} that provides the + * a reference to its source. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +class ConfigurationMetadataItem extends ConfigurationMetadataProperty { + + private String sourceType; + + private String sourceMethod; + + /** + * The class name of the source that contributed this property. For example, if the property + * was from a class annotated with {@code @ConfigurationProperties} this attribute would + * contain the fully qualified name of that class. + */ + public String getSourceType() { + return this.sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + /** + * The full name of the method (include parenthesis and argument types) that contributed this + * property. For example, the name of a getter in a {@code @ConfigurationProperties} annotated + * class. + */ + public String getSourceMethod() { + return this.sourceMethod; + } + + public void setSourceMethod(String sourceMethod) { + this.sourceMethod = sourceMethod; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataProperty.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataProperty.java new file mode 100644 index 0000000000..13fcc86ae9 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataProperty.java @@ -0,0 +1,113 @@ +/* + * 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.configurationmetadata; + +/** + * Define a configuration item. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class ConfigurationMetadataProperty { + + private String id; + + private String name; + + private String type; + + private String description; + + private Object defaultValue; + + private boolean deprecated; + + /** + * The full identifier of the property, in lowercase dashed form (e.g. my.group.simple-property) + */ + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * The name of the property, in lowercase dashed form (e.g. simple-property). If this item + * does not belong to any group, the id is returned. + */ + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The class name of the data type of the property. For example, {@code java.lang.String}. + *

For consistency, the type of a primitive is specified using its wrapper counterpart, + * i.e. {@code boolean} becomes {@code java.lang.Boolean}. Collections type are harmonized + * to their interface counterpart and defines the actual generic types, i.e. + * {@code java.util.HashMap} becomes + * {@code java.util.Map} + *

Note that this class may be a complex type that gets converted from a String as values + * are bound. + */ + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + /** + * A short description of the property, if any. + */ + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The default value, if any. + */ + public Object getDefaultValue() { + return this.defaultValue; + } + + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Specify if the property is deprecated + */ + public boolean isDeprecated() { + return this.deprecated; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepository.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepository.java new file mode 100644 index 0000000000..0086e50847 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepository.java @@ -0,0 +1,46 @@ +/* + * 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.configurationmetadata; + +import java.util.Map; + +/** + * A repository of configuration metadata. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public interface ConfigurationMetadataRepository { + + /** + * Defines the name of the "root" group, that is the group that + * gathers all the properties that aren't attached to a specific + * group. + */ + static final String ROOT_GROUP = "_ROOT_GROUP_"; + + /** + * Return the groups, indexed by id. + */ + Map getAllGroups(); + + /** + * Return the properties, indexed by id. + */ + Map getAllProperties(); + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoader.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoader.java new file mode 100644 index 0000000000..1c62b6ae66 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoader.java @@ -0,0 +1,98 @@ +/* + * 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.configurationmetadata; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; + +import org.json.JSONException; + +/** + * Load a {@link ConfigurationMetadataRepository} from the content of arbitrary resource(s). + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class ConfigurationMetadataRepositoryJsonLoader { + + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + private JsonReader reader = new JsonReader(); + + + /** + * Create a {@link ConfigurationMetadataRepository} with the metadata defined by + * the specified {@code resources} using {@link #UTF_8}. If the same config + * metadata items is held within different resources, the first that is loaded is kept. + */ + public ConfigurationMetadataRepository loadAll(Collection resources) throws IOException { + return loadAll(resources, UTF_8); + } + + /** + * Create a {@link ConfigurationMetadataRepository} with the metadata defined by + * the specified {@code resources} using the specified {@link Charset}. If the + * same config metadata items is held within different resources, the first that + * is loaded is kept. + */ + public ConfigurationMetadataRepository loadAll(Collection resources, Charset charset) throws IOException { + if (resources == null) { + throw new IllegalArgumentException("Resources must not be null."); + } + if (resources.size() == 1) { + return load(resources.iterator().next(), charset); + } + + SimpleConfigurationMetadataRepository repository = new SimpleConfigurationMetadataRepository(); + for (InputStream resource : resources) { + SimpleConfigurationMetadataRepository repo = load(resource, charset); + repository.include(repo); + } + return repository; + } + + private SimpleConfigurationMetadataRepository load(InputStream stream, Charset charset) throws IOException { + try { + RawConfigurationMetadata metadata = this.reader.read(stream, charset); + return create(metadata); + } + catch (IOException e) { + throw new IllegalArgumentException("Failed to read configuration metadata", e); + } + catch (JSONException e) { + throw new IllegalArgumentException("Invalid configuration metadata document", e); + } + } + + private SimpleConfigurationMetadataRepository create(RawConfigurationMetadata metadata) { + SimpleConfigurationMetadataRepository repository = new SimpleConfigurationMetadataRepository(); + repository.add(metadata.getSources()); + for (ConfigurationMetadataItem item : metadata.getItems()) { + ConfigurationMetadataSource source = null; + String sourceType = item.getSourceType(); + if (sourceType != null) { + source = metadata.getSource(sourceType); + } + repository.add(item, source); + } + return repository; + } + +} + diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataSource.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataSource.java new file mode 100644 index 0000000000..dd5a2d8e6b --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/ConfigurationMetadataSource.java @@ -0,0 +1,110 @@ +/* + * 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.configurationmetadata; + +import java.util.HashMap; +import java.util.Map; + +/** + * A source of configuration metadata. Also defines where + * the source is declared, for instance if it is defined + * as a {@code @Bean}. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class ConfigurationMetadataSource { + + private String groupId; + + private String type; + + private String description; + + private String sourceType; + + private String sourceMethod; + + private final Map properties + = new HashMap(); + + /** + * The identifier of the group to which this source is associated + */ + public String getGroupId() { + return this.groupId; + } + + void setGroupId(String groupId) { + this.groupId = groupId; + } + + /** + * The type of the source. Usually this is the fully qualified name + * of a class that defines one more configuration item. This class may or + * may not be available at runtime. + */ + public String getType() { + return this.type; + } + + void setType(String type) { + this.type = type; + } + + /** + * The description of this source, if any. + */ + public String getDescription() { + return this.description; + } + + void setDescription(String description) { + this.description = description; + } + + /** + * The type where this source is defined. This can be identical + * to the {@linkplain #getType() type} if the source is self-defined. + */ + public String getSourceType() { + return this.sourceType; + } + + void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + /** + * The method name that defines this source, if any. + */ + public String getSourceMethod() { + return this.sourceMethod; + } + + void setSourceMethod(String sourceMethod) { + this.sourceMethod = sourceMethod; + } + + /** + * Return the properties defined by this source. + */ + public Map getProperties() { + return this.properties; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/JsonReader.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/JsonReader.java new file mode 100644 index 0000000000..f0ac07fd42 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/JsonReader.java @@ -0,0 +1,116 @@ +/* + * 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.configurationmetadata; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * Read standard json metadata format as {@link ConfigurationMetadataRepository} + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +class JsonReader { + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private static final int BUFFER_SIZE = 4096; + + public RawConfigurationMetadata read(InputStream in) throws IOException { + return read(in, DEFAULT_CHARSET); + } + + public RawConfigurationMetadata read(InputStream in, Charset charset) throws IOException { + JSONObject json = readJson(in, charset); + List groups = parseAllSources(json); + List items = parseAllItems(json); + return new RawConfigurationMetadata(groups, items); + } + + private List parseAllSources(JSONObject root) { + List result = new ArrayList(); + if (!root.has("groups")) { + return result; + } + JSONArray sources = root.getJSONArray("groups"); + for (int i = 0; i < sources.length(); i++) { + JSONObject source = sources.getJSONObject(i); + result.add(parseSource(source)); + } + return result; + } + + private List parseAllItems(JSONObject root) { + List result = new ArrayList(); + if (!root.has("properties")) { + return result; + } + JSONArray items = root.getJSONArray("properties"); + for (int i = 0; i < items.length(); i++) { + JSONObject item = items.getJSONObject(i); + result.add(parseItem(item)); + } + return result; + } + + private ConfigurationMetadataSource parseSource(JSONObject json) { + ConfigurationMetadataSource source = new ConfigurationMetadataSource(); + source.setGroupId(json.getString("name")); + source.setType(json.optString("type", null)); + source.setDescription(json.optString("description", null)); + source.setSourceType(json.optString("sourceType", null)); + source.setSourceMethod(json.optString("sourceMethod", null)); + return source; + } + + private ConfigurationMetadataItem parseItem(JSONObject json) { + ConfigurationMetadataItem item = new ConfigurationMetadataItem(); + item.setId(json.getString("name")); + item.setType(json.optString("type", null)); + item.setDescription(json.optString("description", null)); + item.setDefaultValue(json.opt("defaultValue")); + item.setDeprecated(json.optBoolean("deprecated", false)); + item.setSourceType(json.optString("sourceType", null)); + item.setSourceMethod(json.optString("sourceMethod", null)); + return item; + } + + private JSONObject readJson(InputStream in, Charset charset) throws IOException { + try { + StringBuilder out = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(in, charset); + char[] buffer = new char[BUFFER_SIZE]; + int bytesRead = -1; + while ((bytesRead = reader.read(buffer)) != -1) { + out.append(buffer, 0, bytesRead); + } + return new JSONObject(out.toString()); + } + finally { + in.close(); + } + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/RawConfigurationMetadata.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/RawConfigurationMetadata.java new file mode 100644 index 0000000000..ff953a7580 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/RawConfigurationMetadata.java @@ -0,0 +1,78 @@ +/* + * 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.configurationmetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * A raw metadata structure. Used to initialize a {@link ConfigurationMetadataRepository}. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +class RawConfigurationMetadata { + + private final List sources; + + private final List items; + + RawConfigurationMetadata(List sources, List items) { + this.sources = new ArrayList(sources); + this.items = new ArrayList(items); + for (ConfigurationMetadataItem item : this.items) { + resolveName(item); + } + } + + public List getSources() { + return this.sources; + } + + public ConfigurationMetadataSource getSource(String type) { + for (ConfigurationMetadataSource source : this.sources) { + if (type.equals(source.getType())) { + return source; + } + } + return null; + } + + public List getItems() { + return this.items; + } + + /** + * Resolve the name of an item against this instance. + * @see ConfigurationMetadataProperty#setName(String) + */ + private void resolveName(ConfigurationMetadataItem item) { + item.setName(item.getId()); // fallback + if (item.getSourceType() == null) { + return; + } + ConfigurationMetadataSource source = getSource(item.getSourceType()); + if (source != null) { + String groupId = source.getGroupId(); + String id = item.getId(); + if (id.startsWith(groupId)) { // match + String name = id.substring(groupId.length() + 1, id.length()); // "." + item.setName(name); + } + } + } +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/SimpleConfigurationMetadataRepository.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/SimpleConfigurationMetadataRepository.java new file mode 100644 index 0000000000..87d311fecc --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/configurationmetadata/SimpleConfigurationMetadataRepository.java @@ -0,0 +1,115 @@ +/* + * 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.configurationmetadata; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The default {@link ConfigurationMetadataRepository} implementation. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class SimpleConfigurationMetadataRepository implements ConfigurationMetadataRepository { + + private final Map allGroups = new HashMap(); + + @Override + public Map getAllGroups() { + return Collections.unmodifiableMap(this.allGroups); + } + + @Override + public Map getAllProperties() { + Map properties = new HashMap(); + for (ConfigurationMetadataGroup group : this.allGroups.values()) { + properties.putAll(group.getProperties()); + } + return properties; + } + + /** + * Register the specified {@link ConfigurationMetadataSource sources}. + */ + public void add(Collection sources) { + for (ConfigurationMetadataSource source : sources) { + String groupId = source.getGroupId(); + ConfigurationMetadataGroup group = this.allGroups.get(groupId); + if (group == null) { + group = new ConfigurationMetadataGroup(groupId); + this.allGroups.put(groupId, group); + } + String sourceType = source.getType(); + if (sourceType != null) { + group.getSources().putIfAbsent(sourceType, source); + } + } + } + + /** + * Add a {@link ConfigurationMetadataProperty} with the {@link ConfigurationMetadataSource source} + * that defines it, if any. + */ + public void add(ConfigurationMetadataProperty property, ConfigurationMetadataSource source) { + if (source != null) { + source.getProperties().putIfAbsent(property.getId(), property); + } + getGroup(source).getProperties().putIfAbsent(property.getId(), property); + } + + + /** + * Merge the content of the specified repository to this repository. + */ + public void include(ConfigurationMetadataRepository repository) { + for (ConfigurationMetadataGroup group : repository.getAllGroups().values()) { + ConfigurationMetadataGroup existingGroup = this.allGroups.get(group.getId()); + if (existingGroup == null) { + this.allGroups.put(group.getId(), group); + } + else { + // Merge properties + for (Map.Entry entry : group.getProperties().entrySet()) { + existingGroup.getProperties().putIfAbsent(entry.getKey(), entry.getValue()); + } + // Merge sources + for (Map.Entry entry : group.getSources().entrySet()) { + existingGroup.getSources().putIfAbsent(entry.getKey(), entry.getValue()); + } + } + } + + } + + private ConfigurationMetadataGroup getGroup(ConfigurationMetadataSource source) { + if (source == null) { + ConfigurationMetadataGroup rootGroup = this.allGroups.get(ROOT_GROUP); + if (rootGroup == null) { + rootGroup = new ConfigurationMetadataGroup(ROOT_GROUP); + this.allGroups.put(ROOT_GROUP, rootGroup); + } + return rootGroup; + } + else { + return this.allGroups.get(source.getGroupId()); + } + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/AbstractConfigurationMetadataTests.java b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/AbstractConfigurationMetadataTests.java new file mode 100644 index 0000000000..aa655cbb59 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/AbstractConfigurationMetadataTests.java @@ -0,0 +1,66 @@ +/* + * 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.configurationmetadata; + +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * + * @author Stephane Nicoll + */ +public abstract class AbstractConfigurationMetadataTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + protected void assertSource(ConfigurationMetadataSource actual, String groupId, String type, String sourceType) { + assertNotNull(actual); + assertEquals(groupId, actual.getGroupId()); + assertEquals(type, actual.getType()); + assertEquals(sourceType, actual.getSourceType()); + } + + protected void assertProperty(ConfigurationMetadataProperty actual, String id, String name, + Class type, Object defaultValue) { + assertNotNull(actual); + assertEquals(id, actual.getId()); + assertEquals(name, actual.getName()); + String typeName = type != null ? type.getName() : null; + assertEquals(typeName, actual.getType()); + assertEquals(defaultValue, actual.getDefaultValue()); + } + + protected void assertItem(ConfigurationMetadataItem actual, String sourceType) { + assertNotNull(actual); + assertEquals(sourceType, actual.getSourceType()); + } + + protected InputStream getInputStreamFor(String name) throws IOException { + Resource r = new ClassPathResource("metadata/configuration-metadata-" + name + ".json"); + return r.getInputStream(); + } +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoaderTests.java b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoaderTests.java new file mode 100644 index 0000000000..8d8fbc5a82 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/ConfigurationMetadataRepositoryJsonLoaderTests.java @@ -0,0 +1,122 @@ +/* + * 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.configurationmetadata; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link ConfigurationMetadataRepository}. + * + * @author Stephane Nicoll + */ +public class ConfigurationMetadataRepositoryJsonLoaderTests extends AbstractConfigurationMetadataTests { + + private final ConfigurationMetadataRepositoryJsonLoader loader = new ConfigurationMetadataRepositoryJsonLoader(); + + @Test + public void nullResource() throws IOException { + thrown.expect(IllegalArgumentException.class); + loader.loadAll(null); + } + + @Test + public void simpleRepository() throws IOException { + ConfigurationMetadataRepository repo = loader.loadAll(Collections.singleton(getInputStreamFor("foo"))); + validateFoo(repo); + assertEquals(1, repo.getAllGroups().size()); + + contains(repo.getAllProperties(), "spring.foo.name", "spring.foo.description", "spring.foo.counter"); + assertEquals(3, repo.getAllProperties().size()); + } + + @Test + public void severalRepositoriesNoConflict() throws IOException { + ConfigurationMetadataRepository repo = loader.loadAll( + Arrays.asList(getInputStreamFor("foo"), getInputStreamFor("bar"))); + validateFoo(repo); + validateBar(repo); + assertEquals(2, repo.getAllGroups().size()); + + contains(repo.getAllProperties(), "spring.foo.name", "spring.foo.description", "spring.foo.counter", + "spring.bar.name", "spring.bar.description", "spring.bar.counter"); + assertEquals(6, repo.getAllProperties().size()); + } + + @Test + public void repositoryWithRoot() throws IOException { + ConfigurationMetadataRepository repo = loader.loadAll( + Arrays.asList(getInputStreamFor("foo"), getInputStreamFor("root"))); + validateFoo(repo); + assertEquals(2, repo.getAllGroups().size()); + + contains(repo.getAllProperties(), "spring.foo.name", "spring.foo.description", "spring.foo.counter", + "spring.root.name", "spring.root2.name"); + assertEquals(5, repo.getAllProperties().size()); + } + + @Test + public void severalRepositoriesIdenticalGroups() throws IOException { + ConfigurationMetadataRepository repo = loader.loadAll( + Arrays.asList(getInputStreamFor("foo"), getInputStreamFor("foo2"))); + assertEquals(1, repo.getAllGroups().size()); + ConfigurationMetadataGroup group = repo.getAllGroups().get("spring.foo"); + contains(group.getSources(), "org.acme.Foo", "org.acme.Foo2", "org.springframework.boot.FooProperties"); + assertEquals(3, group.getSources().size()); + contains(group.getProperties(), "spring.foo.name", "spring.foo.description", "spring.foo.counter", + "spring.foo.enabled", "spring.foo.type"); + assertEquals(5, group.getProperties().size()); + contains(repo.getAllProperties(), "spring.foo.name", "spring.foo.description", "spring.foo.counter", + "spring.foo.enabled", "spring.foo.type"); + assertEquals(5, repo.getAllProperties().size()); + } + + private void validateFoo(ConfigurationMetadataRepository repo) { + ConfigurationMetadataGroup group = repo.getAllGroups().get("spring.foo"); + contains(group.getSources(), "org.acme.Foo", "org.springframework.boot.FooProperties"); + ConfigurationMetadataSource source = group.getSources().get("org.acme.Foo"); + contains(source.getProperties(), "spring.foo.name", "spring.foo.description"); + assertEquals(2, source.getProperties().size()); + ConfigurationMetadataSource source2 = group.getSources().get("org.springframework.boot.FooProperties"); + contains(source2.getProperties(), "spring.foo.name", "spring.foo.counter"); + assertEquals(2, source2.getProperties().size()); + } + + private void validateBar(ConfigurationMetadataRepository repo) { + ConfigurationMetadataGroup group = repo.getAllGroups().get("spring.bar"); + contains(group.getSources(), "org.acme.Bar", "org.springframework.boot.BarProperties"); + ConfigurationMetadataSource source = group.getSources().get("org.acme.Bar"); + contains(source.getProperties(), "spring.bar.name", "spring.bar.description"); + assertEquals(2, source.getProperties().size()); + ConfigurationMetadataSource source2 = group.getSources().get("org.springframework.boot.BarProperties"); + contains(source2.getProperties(), "spring.bar.name", "spring.bar.counter"); + assertEquals(2, source2.getProperties().size()); + } + + private void contains(Map source, String... keys) { + for (String key : keys) { + assertTrue("Item '" + key + "' not found. Got " + source.keySet(), source.containsKey(key)); + } + } +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/JsonReaderTests.java b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/JsonReaderTests.java new file mode 100644 index 0000000000..997d552d99 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/configurationmetadata/JsonReaderTests.java @@ -0,0 +1,86 @@ +/* + * 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.configurationmetadata; + +import java.io.IOException; +import java.util.List; + +import org.json.JSONException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests for {@link JsonReader} + * + * @author Stephane Nicoll + */ +public class JsonReaderTests extends AbstractConfigurationMetadataTests { + + private final JsonReader reader = new JsonReader(); + + @Test + public void emptyMetadata() throws IOException { + RawConfigurationMetadata rawMetadata = reader.read(getInputStreamFor("empty")); + assertEquals(0, rawMetadata.getSources().size()); + assertEquals(0, rawMetadata.getItems().size()); + } + + @Test + public void invalidMetadata() throws IOException { + thrown.expect(JSONException.class); + reader.read(getInputStreamFor("invalid")); + } + + @Test + public void simpleMetadata() throws IOException { + RawConfigurationMetadata rawMetadata = reader.read(getInputStreamFor("foo")); + List sources = rawMetadata.getSources(); + assertEquals(2, sources.size()); + List items = rawMetadata.getItems(); + assertEquals(4, items.size()); + + + ConfigurationMetadataSource source = sources.get(0); + assertSource(source, "spring.foo", "org.acme.Foo", "org.acme.config.FooApp"); + assertEquals("foo()", source.getSourceMethod()); + assertEquals("This is Foo.", source.getDescription()); + ConfigurationMetadataItem item = items.get(0); + assertProperty(item, "spring.foo.name", "name", String.class, null); + assertItem(item, "org.acme.Foo"); + ConfigurationMetadataItem item2 = items.get(1); + assertProperty(item2, "spring.foo.description", "description", String.class, "FooBar"); + assertEquals("Foo description.", item2.getDescription()); + assertNull(item2.getSourceMethod()); + assertItem(item2, "org.acme.Foo"); + } + + @Test + public void rootMetadata() throws IOException { + RawConfigurationMetadata rawMetadata = reader.read(getInputStreamFor("root")); + List sources = rawMetadata.getSources(); + assertEquals(0, sources.size()); + List items = rawMetadata.getItems(); + assertEquals(2, items.size()); + + ConfigurationMetadataItem item = items.get(0); + assertProperty(item, "spring.root.name", "spring.root.name", String.class, null); + + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-bar.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-bar.json new file mode 100644 index 0000000000..3e1d9e124d --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-bar.json @@ -0,0 +1,40 @@ +{ + "groups": [ + { + "name": "spring.bar", + "type": "org.acme.Bar", + "sourceType": "org.acme.config.BarApp", + "sourceMethod": "bar()", + "description": "This is Bar." + }, + { + "name": "spring.bar", + "type": "org.springframework.boot.BarProperties" + } + ], + "properties": [ + { + "name": "spring.bar.name", + "type": "java.lang.String", + "sourceType": "org.acme.Bar" + }, + { + "name": "spring.bar.description", + "type": "java.lang.String", + "sourceType": "org.acme.Bar", + "description": "Bar description.", + "defaultValue": "BarFoo" + }, + { + "name": "spring.bar.name", + "type": "java.lang.String", + "sourceType": "org.springframework.boot.BarProperties" + }, + { + "name": "spring.bar.counter", + "type": "java.lang.Integer", + "sourceType": "org.springframework.boot.BarProperties", + "defaultValue": 0 + } + ] +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-empty.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-empty.json new file mode 100644 index 0000000000..b42f309e7a --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-empty.json @@ -0,0 +1,3 @@ +{ + "foo": "bar" +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo.json new file mode 100644 index 0000000000..4ddc86da17 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo.json @@ -0,0 +1,40 @@ +{ + "groups": [ + { + "name": "spring.foo", + "type": "org.acme.Foo", + "sourceType": "org.acme.config.FooApp", + "sourceMethod": "foo()", + "description": "This is Foo." + }, + { + "name": "spring.foo", + "type": "org.springframework.boot.FooProperties" + } + ], + "properties": [ + { + "name": "spring.foo.name", + "type": "java.lang.String", + "sourceType": "org.acme.Foo" + }, + { + "name": "spring.foo.description", + "type": "java.lang.String", + "sourceType": "org.acme.Foo", + "description": "Foo description.", + "defaultValue": "FooBar" + }, + { + "name": "spring.foo.name", + "type": "java.lang.String", + "sourceType": "org.springframework.boot.FooProperties" + }, + { + "name": "spring.foo.counter", + "type": "java.lang.Integer", + "sourceType": "org.springframework.boot.FooProperties", + "defaultValue": 0 + } + ] +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo2.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo2.json new file mode 100644 index 0000000000..a57f4992cf --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-foo2.json @@ -0,0 +1,23 @@ +{ + "groups": [ + { + "name": "spring.foo", + "type": "org.acme.Foo2", + "sourceType": "org.acme.config.FooApp", + "sourceMethod": "foo2()", + "description": "This is Foo2." + } + ], + "properties": [ + { + "name": "spring.foo.enabled", + "type": "java.lang.Boolean", + "sourceType": "org.acme.Foo2" + }, + { + "name": "spring.foo.type", + "type": "java.lang.String", + "sourceType": "org.acme.Foo2" + } + ] +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-invalid.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-invalid.json new file mode 100644 index 0000000000..ca2f071115 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-invalid.json @@ -0,0 +1,8 @@ +{ + "properties": [ + { + "type": "java.lang.String", + "sourceType": "org.acme.Invalid" + } + ] +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-root.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-root.json new file mode 100644 index 0000000000..8fced8db7c --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-root.json @@ -0,0 +1,11 @@ +{ + "properties": [ + { + "name": "spring.root.name", + "type": "java.lang.String" + }, + { + "name": "spring.root2.name" + } + ] +} \ No newline at end of file