Description
Constructing a ConfigurationMetadataRepository from two distinct metadata file leads to incorrect results when the files contain properties that belong to the same group.
Here is a scenario to illustrate the case.
Suppose the two following spring-configuration-metadata files. As you can see, they hold properties of different names but they all belong to the same group:
----------
meta1.json
----------
{
"groups": [
{
"name": "server",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
}
],
"properties": [
{
"name": "server.port",
"type": "java.lang.Integer",
"description": "Server HTTP port.",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
}
]
}
----------
meta2.json
----------
{
"groups": [
{
"name": "server",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
}
],
"properties": [
{
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
"name": "server.address",
"description": "Network address to which the server should bind to.",
"type": "java.net.InetAddress"
}
]
}
Now I try to read these two files into the same ConfigurationMetadataRepository
as follows:
ConfigurationMetadataRepositoryJsonBuilder metadataBuilder = ConfigurationMetadataRepositoryJsonBuilder.create();
metadataBuilder.withJsonResource(new FileInputStream("meta1.json"));
metadataBuilder.withJsonResource(new FileInputStream("meta2.json"));
ConfigurationMetadataRepository metadata = metadataBuilder.build();
As shown below, #getAllProperties()
returns both properties as expected, and #getAllGroups()
returns the only server
group. Listing the properties of that group correctly returns to two properties as well. All this seems to work as expected.
metadata.getAllProperties() --> server.port
server.address
metadata.getAllGroups() --> server
"server group".getProperties() --> server.port
server.address
The "server" group contains a single "source" (as expected). However, listing the properties of that single source returns only the server.port
address, not both as I would expect.
"server group".getSources() --> server
"server group".getSources().getProperties() --> server.port
As far as I understand the model the following should apply:
- group.properties should contain all properties of all the sources
- the union of all source.properties should match group.properties
The last assumption is not verified in my test scenario.
According to me the issue is in the merge logic implemented in SimpleConfigurationMetadata#include
..
The repository already contains a "server" group when the second metadata file is loaded and the include method tries to merge the content of the new group into the existing one. This is done by adding the properties of the new group into the existing one if they are missing. When done, the logic goes further by "merging" the sources. However, the merge logic for the sources seems incorrect: their properties should be merged as well.
Stated differently, adding the following method should solve the issue:
private void putIfAbsent(Map<String, ConfigurationMetadataSource> sources, String name, ConfigurationMetadataSource source) {
ConfigurationMetadataSource existing = sources.get(name);
if (existing==null) {
sources.put(name, source);
}
else {
source.getProperties().forEach((k,v) -> putIfAbsent(existing.getProperties(), k, v));
}
}