Skip to content

Commit

Permalink
Added support for Spring profiles
Browse files Browse the repository at this point in the history
- default values are extracted for each profile
- profiles can be filtered through pom
- updated tests
  • Loading branch information
egoettelmann committed Jul 15, 2023
1 parent ec8d833 commit 264ee32
Show file tree
Hide file tree
Showing 15 changed files with 227 additions and 50 deletions.
6 changes: 4 additions & 2 deletions spring-configuration-aggregator-maven-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ All found files will then be merged into a single file, with following attribute
- the `name` of the configuration property used
- the `type` of the attribute
- the `description` taken from the JavaDoc defined on the attribute (if any)
- the `defaultValue` specified in the annotation (if any)
- the `sourceTypes` the list of references to this property:
- the `defaultValue` specified in the annotation, or in the properties file (if any)
- the `profiles` maps all defined values per Spring profile
- the `sourceTypes` lists all references to this property:
- the `groupId`, the group id of the dependency
- the `artifactId`, the artifact id of the dependency
- the `sourceType`, the class referencing this property
Expand Down Expand Up @@ -68,6 +69,7 @@ The plugin can be configured through following properties:
| `includeDependencies` | `List<DependencyMatcher>` | Specifies a list of dependencies to include for aggregation. |
| `excludeDependencies` | `List<DependencyMatcher>` | Specifies a list of dependencies to exclude from aggregation. |
| `propertiesFiles` | `List<PropertiesFile>` | Specifies a list of properties files to parse for default values. |
| `profiles` | `String` | Comma separated list of Spring profiles to include values for. |
| `outputReports` | `List<OutputReport>` | Specifies a list of reports to generate. |

The `DependencyMatcher` type is defined with following properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
* Aggregates all Spring Configuration Properties metadata into a single file.
Expand All @@ -27,6 +27,13 @@ public class AggregatorMojo extends AbstractPluginMojo {
@Parameter()
private List<PropertiesFile> propertiesFiles;

/**
* Comma separated list of spring profiles to include for reading values ('*' for all, '-' for none).
* Default value: '*'.
*/
@Parameter()
private String profiles;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.skip) {
Expand All @@ -52,7 +59,10 @@ public void execute() throws MojoExecutionException, MojoFailureException {
);

// Retrieving properties
final List<AggregatedPropertyMetadata> properties = aggregationService.aggregate(this.getPropertiesFiles());
final List<AggregatedPropertyMetadata> properties = aggregationService.aggregate(
this.getPropertiesFiles(),
this.getProfiles()
);

// Saving properties
aggregationService.save(properties);
Expand All @@ -79,4 +89,20 @@ private List<PropertiesFile> getPropertiesFiles() {
return files;
}

private Set<String> getProfiles() {
// Default value: all found profiles (null)
if (this.profiles == null || this.profiles.equals("*")) {
return null;
}

// No profile included: empty set
if (this.profiles.equals("-")) {
return Collections.emptySet();
}

// Split and trim values
return Arrays.stream(this.profiles.split(","))
.map(String::trim)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public class AggregationBuilder {

private final Map<Pair<String, String>, List<PropertyMetadata>> artifactProperties;

private final Properties defaultValues;
private final Map<String, Properties> defaultValues;

public AggregationBuilder(final Log log) {
this.log = log;
this.artifactProperties = new HashMap<>();
this.defaultValues = new Properties();
this.defaultValues = new HashMap<>();
}

public AggregationBuilder add(final List<PropertyMetadata> metadata, final String groupId, final String artifactId) {
Expand All @@ -38,8 +38,11 @@ public AggregationBuilder add(final List<PropertyMetadata> metadata, final Strin
return this;
}

public AggregationBuilder put(final Properties defaultValues) {
this.defaultValues.putAll(defaultValues);
public AggregationBuilder put(final String profile, final Properties properties) {
if (!this.defaultValues.containsKey(profile)) {
this.defaultValues.put(profile, new Properties());
}
this.defaultValues.get(profile).putAll(properties);
return this;
}

Expand Down Expand Up @@ -81,8 +84,22 @@ private AggregatedPropertyMetadata create(final PropertyMetadata property, final
aggregate.setName(property.getName());
aggregate.setDefaultValue(property.getDefaultValue());
// If a default value is defined, it overrides the already defined one
if (this.defaultValues.containsKey(property.getName())) {
aggregate.setDefaultValue(this.defaultValues.getProperty(property.getName()));
for (final Map.Entry<String, Properties> entry : this.defaultValues.entrySet()) {
final String profile = entry.getKey();
final Properties profileProperties = entry.getValue();
this.log.debug("Found " + profileProperties.size() + " values for profile " + profile);

if (!profileProperties.containsKey(property.getName())) {
continue;
}
if (DefaultAggregationService.DEFAULT_PROFILE.equals(profile)) {
aggregate.setDefaultValue(profileProperties.getProperty(property.getName()));
continue;
}
if (aggregate.getProfiles() == null) {
aggregate.setProfiles(new HashMap<>());
}
aggregate.getProfiles().put(profile, profileProperties.getProperty(property.getName()));
}
aggregate.setType(property.getType());
aggregate.setDescription(property.getDescription());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
import com.github.egoettelmann.spring.configuration.extensions.aggregator.maven.core.exceptions.OperationFailedException;
import com.github.egoettelmann.spring.configuration.extensions.aggregator.maven.core.model.AggregatedPropertyMetadata;
import com.github.egoettelmann.spring.configuration.extensions.aggregator.maven.core.model.PropertyMetadata;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.artifact.Artifact;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.*;

public class DefaultAggregationService implements AggregationService {

Expand All @@ -28,6 +30,8 @@ public class DefaultAggregationService implements AggregationService {

private static final String AGGREGATED_FILE = "/META-INF/aggregated-spring-configuration-metadata.json";

public final static String DEFAULT_PROFILE = "default";

private final Log log;

private final RepositoryService repositoryService;
Expand Down Expand Up @@ -58,7 +62,7 @@ public DefaultAggregationService(
}

@Override
public List<AggregatedPropertyMetadata> aggregate(final List<PropertiesFile> propertiesFiles) {
public List<AggregatedPropertyMetadata> aggregate(final List<PropertiesFile> propertiesFiles, final Set<String> profiles) {
final AggregationBuilder builder = new AggregationBuilder(this.log);

// Resolving from current project
Expand All @@ -79,12 +83,11 @@ public List<AggregatedPropertyMetadata> aggregate(final List<PropertiesFile> pro
// Adding default values
if (propertiesFiles != null) {
for (final PropertiesFile propertiesFile : propertiesFiles) {
try {
final Properties properties = this.propertiesValueReader.read("file:///" + propertiesFile.getPath());
builder.put(properties);
} catch (final MetadataFileNotFoundException e) {
this.log.warn("No properties found in " + propertiesFile.getPath());
this.log.debug(e);
final Map<String, Properties> properties = this.loadPropertiesValues(propertiesFile.getPath());
for (final Map.Entry<String, Properties> entry : properties.entrySet()) {
if (profiles == null || profiles.contains(entry.getKey())) {
builder.put(entry.getKey(), entry.getValue());
}
}
}
}
Expand Down Expand Up @@ -140,4 +143,66 @@ private List<PropertyMetadata> readPropertiesFromPath(final String path) {
return properties;
}

private Map<String, Properties> loadPropertiesValues(final String path) {
final Map<String, Properties> properties = new HashMap<>();
properties.put(DEFAULT_PROFILE, new Properties());

final File propertiesFile = new File(path);
try {
final List<Properties> propertiesList = this.propertiesValueReader.read("file:///" + path);
for (final Properties values : propertiesList) {
final String profiles = (String) values.getOrDefault("spring.profiles", values.get("spring.config.activate.on-profile"));
if (StringUtils.isBlank(profiles)) {
properties.get(DEFAULT_PROFILE).putAll(values);
continue;
}
for (final String profile : profiles.split(",")) {
properties.putIfAbsent(profile.trim(), new Properties());
properties.get(profile.trim()).putAll(values);
}
}
} catch (final MetadataFileNotFoundException e) {
this.log.warn("No properties found in " + path);
this.log.debug(e);
}

final File folder = propertiesFile.getParentFile();
final String baseName = FilenameUtils.getBaseName(propertiesFile.getName());
final String extension = FilenameUtils.getExtension(propertiesFile.getName());
final FileFilter fileFilter = new WildcardFileFilter(baseName + "-*." + extension);
this.log.debug("Looking up " + folder.getPath() + " for '" + baseName + "-*." + extension + "'");
final File[] files = folder.listFiles(fileFilter);
if (files == null) {
return properties;
}
this.log.debug("Found " + files.length + " profile specific files to parse");

// Reading profile specific files
for (final File file : files) {
try {
final String profile = FilenameUtils.getBaseName(file.getName()).replace(baseName + "-", "").trim();
this.log.debug("Parsing file " + file.getPath() + " for profile " + profile);
final List<Properties> profilePropertiesList = this.propertiesValueReader.read("file:///" + file.getPath());
for (final Properties values : profilePropertiesList) {
final String subProfiles = (String) values.getOrDefault("spring.profiles", values.get("spring.config.activate.on-profile"));
if (StringUtils.isBlank(subProfiles)) {
properties.putIfAbsent(profile, new Properties());
properties.get(profile).putAll(values);
continue;
}
for (final String subProfile : subProfiles.split(",")) {
final String combinedProfile = profile + " & " + subProfile.trim();
properties.putIfAbsent(combinedProfile, new Properties());
properties.get(combinedProfile).putAll(values);
}
}
} catch (final Exception e) {
this.log.warn("Error reading profile specific file " + file.getPath());
this.log.debug(e);
}
}

return properties;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.egoettelmann.spring.configuration.extensions.aggregator.maven.components.aggregation;

import com.github.egoettelmann.spring.configuration.extensions.aggregator.maven.core.exceptions.MetadataFileNotFoundException;
import org.apache.commons.io.FilenameUtils;
import org.apache.maven.plugin.logging.Log;
import org.yaml.snakeyaml.Yaml;

Expand All @@ -18,24 +19,29 @@ public PropertiesValueReader(final Log log) {
this.log = log;
}

public Properties read(final String filePath) throws MetadataFileNotFoundException {
public List<Properties> read(final String filePath) throws MetadataFileNotFoundException {
// Parsing filePath as URL
final URL propertiesUrl;
try {
propertiesUrl = new URL(filePath);
} catch (final MalformedURLException e) {
throw new MetadataFileNotFoundException("Invalid file path " + filePath, e);
}
final String extension = FilenameUtils.getExtension(propertiesUrl.getFile());
this.log.debug("Reading file '" + filePath + "' with extension '" + extension + "'");

final String extension = filePath.substring(filePath.lastIndexOf("."));
if (".properties".equalsIgnoreCase(extension)) {
return this.readAsProperties(propertiesUrl);
// Parsing as properties
if ("properties".equalsIgnoreCase(extension)) {
return Collections.singletonList(this.readAsProperties(propertiesUrl));
}
if (".yml".equalsIgnoreCase(extension) || ".yaml".equalsIgnoreCase(extension)) {

// Parsing as yaml
if ("yml".equalsIgnoreCase(extension) || "yaml".equalsIgnoreCase(extension)) {
return this.readAsYaml(propertiesUrl);
}

throw new MetadataFileNotFoundException("Unsupported file extension " + extension + " for " + propertiesUrl);
// Not a valid extension
throw new MetadataFileNotFoundException("Unsupported file extension " + extension + " for " + propertiesUrl.getFile());
}

private Properties readAsProperties(final URL propertiesUrl) throws MetadataFileNotFoundException {
Expand All @@ -45,33 +51,51 @@ private Properties readAsProperties(final URL propertiesUrl) throws MetadataFile
// Parsing content
final Properties properties = new Properties();
properties.load(fileContent);
this.log.debug("Found " + properties.size() + " property values in file " + propertiesUrl);

// Logging all found properties for debug
if (this.log.isDebugEnabled()) {
this.log.debug("Found " + properties.size() + " property values in file " + propertiesUrl.getFile());
for (final Map.Entry<Object, Object> property : properties.entrySet()) {
this.log.debug("Found '" + property.getKey() + "=" + property.getValue() + "'");
}
}

// Returning parsed properties
return properties;
} catch (final IOException e) {
throw new MetadataFileNotFoundException("Failed reading " + propertiesUrl, e);
throw new MetadataFileNotFoundException("Failed reading " + propertiesUrl.getFile(), e);
}
}

private Properties readAsYaml(final URL propertiesUrl) throws MetadataFileNotFoundException {
private List<Properties> readAsYaml(final URL propertiesUrl) throws MetadataFileNotFoundException {
final List<Properties> propertiesList = new ArrayList<>();

try (final InputStream fileContent = propertiesUrl.openStream()) {
this.log.debug("Found property values file " + propertiesUrl.getFile());

// Parsing content
final Properties properties = new Properties();
// Parsing content: yaml file may contain multiple documents
final Yaml yaml = new Yaml();
final Map<String, String> flattened = this.flatten(yaml.load(fileContent));
properties.putAll(flattened);
this.log.debug("Found " + properties.size() + " property values in file " + propertiesUrl);
for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
this.log.debug("Found '" + entry.getKey() + "=" + entry.getValue() + "'");
for (final Object group : yaml.loadAll(fileContent)) {
final Map<String, String> flattened = this.flatten((Map<String, Object>) group);
final Properties properties = new Properties();
properties.putAll(flattened);
propertiesList.add(properties);
}

// Logging all found properties for debug
if (this.log.isDebugEnabled()) {
for (final Properties properties : propertiesList) {
this.log.debug("Found " + properties.size() + " property values in file " + propertiesUrl.getFile());
for (Map.Entry<Object, Object> property : properties.entrySet()) {
this.log.debug("Found '" + property.getKey() + "=" + property.getValue() + "'");
}
}
}

// Returning parsed properties
return properties;
return propertiesList;
} catch (final IOException e) {
throw new MetadataFileNotFoundException("Failed reading " + propertiesUrl, e);
throw new MetadataFileNotFoundException("Failed reading " + propertiesUrl.getFile(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public ArtifactMetadata report(final List<AggregatedPropertyMetadata> aggregate)
this.log.warn("No aggregation file found for previous version, skipping comparison");
this.log.debug(e);
} catch (final Exception e) {
this.log.warn("Failed to compare with previous version", e);
this.log.warn("Failed to compare with previous version");
this.log.debug(e);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.eclipse.aether.artifact.Artifact;

import java.util.List;
import java.util.Set;

public interface AggregationService {

Expand All @@ -14,9 +15,10 @@ public interface AggregationService {
* Combines all metadata files from the project and of its dependencies.
*
* @param propertiesFiles the list of properties files to extract default values from
* @param profiles the list of spring profiles to extract default values from (can be null to accept all)
* @return the list of aggregated properties metadata
*/
List<AggregatedPropertyMetadata> aggregate(final List<PropertiesFile> propertiesFiles);
List<AggregatedPropertyMetadata> aggregate(final List<PropertiesFile> propertiesFiles, final Set<String> profiles);

/**
* Loads the aggregated configuration properties metadata of the current project.
Expand Down
Loading

0 comments on commit 264ee32

Please sign in to comment.