io.knowledgelinks.cicd.semantic.versioning.config.Configuration Maven / Gradle / Ivy
package io.knowledgelinks.cicd.semantic.versioning.config;
import java.io.File;
/*-
* #%L
* Semantic Versioning
* %%
* Copyright (C) 2022 - 2023 Knowledgelinks
* %%
* 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.
* #L%
*/
import java.io.FileInputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.knowledgelinks.cicd.semantic.versioning.exceptions.SemanticVersioningException;
import io.knowledgelinks.cicd.semantic.versioning.formatters.StandardVersionFormatter;
import io.knowledgelinks.cicd.semantic.versioning.parsers.StandardVersionPatternParser;
/**
* The class that holds the configuration for the application. When instantiated it will set
* configuration values from system properties, properties files, environment variables and directly
* specified values.
*
* The class can be used as a singleton through the getInstance method.
*
* @author mstabile75
*
*/
@JsonSerialize(using = ConfigurationSerializer.class)
public class Configuration {
private static Logger logger = LoggerFactory.getLogger(Configuration.class);
private static Configuration config = null;
Map configurableFields = new HashMap<>();
ConfigDescriptor[] configurableFieldsArray;
private Properties runtimeProperties;
private static String apiVersion;
public Configuration() throws SemanticVersioningException {
initialize(null);
}
/**
* Specifying a configuration mapping that will override other variables.
*
* @param configMap [Optional] map of configuration values
* @throws SemanticVersioningException thrown if unable to create an instance
*/
public Configuration(Map configMap) throws SemanticVersioningException {
initialize(configMap);
}
/**
* Sets the static instance of the of the configuration
*
* @param configuration the configuration to use as the default instance
*/
public static void setInstance(Configuration configuration) {
Configuration.config = configuration;
}
/**
* Returns the static configuration instance
*
* @return the default instance of the configuration
* @throws SemanticVersioningException thrown if unable to generate the configuration
*/
public static Configuration getInstance() throws SemanticVersioningException {
if (config == null) {
config = new Configuration();
}
return config;
}
/**
* Generates all of the configurable fields and places them in an array to preserve order and a
* HashMap to enable ease of lookup. This approach is used so that a configurable item can be
* stored in an object with its description, default value and runtime value. This allows for
* easily generating template environment and system property files as well as updating the README
* with available configuration information.
*/
private void generateConfigurationMap() {
String semanticSeparator = "\\.";
String detailsSeparator = "-";
ConfigDescriptor[] configFields = {
new ConfigDescriptor("featureBranchIdentifier", String.class, "feature/",
"The start of a branch string flagged for removal in versioning.", true, true),
new ConfigDescriptor("semanticSeparator", String.class, semanticSeparator,
"The string value used to separate the major, minor and patch values", true, true),
new ConfigDescriptor("detailsSeparator", String.class, detailsSeparator,
"The string value used to separate the branch, and build numbers in the version string",
true, true),
new ConfigDescriptor("releaseFormat", String.class,
String.format("{MAJOR}%s{MINOR}%s{PATCH}", semanticSeparator.replace("\\", ""),
semanticSeparator.replace("\\", "")),
"The pattern used to generate a release version. The {} markers are replaced.", true,
true),
new ConfigDescriptor("snapshotFormat", String.class,
String.format("%s%s%s%s%s%s%s%s%s", Globals.MAJOR_MARKER,
semanticSeparator.replace("\\", ""), Globals.MINOR_MARKER,
semanticSeparator.replace("\\", ""), Globals.PATCH_MARKER, detailsSeparator,
Globals.BRANCH_MARKER, detailsSeparator, Globals.BUILD_NUMBER_MARKER),
"The pattern used to generate a snapshot version. The {} markers are replaced.", true,
true),
new ConfigDescriptor("masterFormat", String.class,
String.format("%s%s%s%s%s%s%s", Globals.MAJOR_MARKER,
semanticSeparator.replace("\\", ""), Globals.MINOR_MARKER,
semanticSeparator.replace("\\", ""), Globals.PATCH_MARKER, detailsSeparator,
Globals.BUILD_NUMBER_MARKER),
"The pattern used to generate a master branch version. The {} markers are replaced.",
true, true),
new ConfigDescriptor("javaArtifactSnapshotFormat", String.class,
String.format("%s%s%s%sSNAPSHOT.%s", Globals.ARTIFACT_NAME_MARKER, detailsSeparator,
Globals.VERSION_MARKER, detailsSeparator, Globals.FILE_EXTENSION_MARKER),
"The format pattern to use for java snapshot artifacts.", true, true),
new ConfigDescriptor("javaArtifactReleaseFormat", String.class,
String.format("%s%s%s.%s", Globals.ARTIFACT_NAME_MARKER, detailsSeparator,
Globals.VERSION_MARKER, Globals.FILE_EXTENSION_MARKER),
"The format pattern to use for java release artifacts.", true, true),
new ConfigDescriptor("archiveArtifactSnapshotFormat", String.class,
String.format("%s%s%s%sSNAPSHOT.%s", Globals.ARTIFACT_NAME_MARKER, detailsSeparator,
Globals.VERSION_MARKER, detailsSeparator, Globals.FILE_EXTENSION_MARKER),
"The format pattern to use for all archive type snapshot artifacts.", true, true),
new ConfigDescriptor("archiveArtifactReleaseFormat", String.class,
String.format("%s%s%s.%s", Globals.ARTIFACT_NAME_MARKER, detailsSeparator,
Globals.VERSION_MARKER, Globals.FILE_EXTENSION_MARKER),
"The format pattern to use for all archive type release artifacts.", true, true),
new ConfigDescriptor("branchTrimLength", Integer.class, "30",
"The max length to trim a branch name in a version.", true, true),
new ConfigDescriptor("buildNumberPadLength", Integer.class, "3",
"The number of zeros to pad the build number.", true, true),
new ConfigDescriptor("defaultVersionFormatter", String.class,
StandardVersionFormatter.class.getName(),
"The class name of the VersionFormatter to use as default.", true, true),
new ConfigDescriptor("defaultVersionParser", String.class,
StandardVersionPatternParser.class.getName(),
"The class name of the VersionParser to use as default", true, true),
new ConfigDescriptor("masterBranchName", String.class, Globals.DEFAULT_MASTER_BRANCH_NAME,
"The master branch name for the repository.", true, true),
new ConfigDescriptor("releaseBranchName", String.class, Globals.DEFAULT_RELEASE_BRANCH_NAME,
"The release branch name for the repository.", true, true),
new ConfigDescriptor("workingDirectory", String.class, System.getProperty("user.dir"),
"The working directory to use for all git calls.", true, true)};
Arrays.stream(configFields).forEach(item -> configurableFields.put(item.getFieldName(), item));
configurableFieldsArray = configFields;
}
/**
* Initializes the configuration. Value are applied in a cascade. Order of value setting is a
* follows: - field defaults - jar properties files - specified properties file - system
* properties - environment variables - map of values passed i.e. setting a value in the configMap
* will override all other settings
*
* @param configMap [Optional] map of configuration values
* @throws SemanticVersioningException thrown if unable to initialize
*/
private void initialize(Map configMap) throws SemanticVersioningException {
generateConfigurationMap();
URL defaultPropertiesUrl =
Thread.currentThread().getContextClassLoader().getResource("application.properties");
String defaultPropertiesFile = defaultPropertiesUrl.getPath();
setApiVersion(defaultPropertiesFile);
String systemPropertiesSpecifiedPropertiesFile =
System.getProperty(Globals.SYSTEM_PROPERTIES_SPECIFIED_PROPERTIES_FILE_KEY);
if (systemPropertiesSpecifiedPropertiesFile != null
&& !systemPropertiesSpecifiedPropertiesFile.isBlank()) {
if (!(new File(systemPropertiesSpecifiedPropertiesFile).exists())) {
throw new SemanticVersioningException(
String.format("The specified properties file does not exists: %s",
systemPropertiesSpecifiedPropertiesFile));
}
}
// class field values are now set through each call so that the proper cascade is achieved
loadProperties(defaultPropertiesFile);
runtimeProperties = loadProperties(systemPropertiesSpecifiedPropertiesFile);
applyProperties(System.getProperties());
loadEnv();
if (configMap != null) {
loadMap(configMap);
}
}
/**
* Loads the configuration mapping into the instance. The configMap uses the same keys as the
* environment variables
*
* @param configMap
*/
private void loadMap(Map configMap) {
for (ConfigDescriptor field : configurableFields.values()) {
String mapValue;
try {
mapValue = (String) configMap.get(field.getEnvName(true));
} catch (Exception e) {
mapValue = null;
}
if (mapValue != null) {
field.setStringValue(mapValue);
} else {
// look for environment variables that do not have the header value
try {
mapValue = (String) configMap.get(field.getEnvName(false));
} catch (Exception e) {
mapValue = null;
}
{
if (mapValue != null) {
field.setStringValue(mapValue);
}
}
}
}
}
/**
* Sets the class field values to the related environment variable values if set.
*/
private void loadEnv() {
for (ConfigDescriptor field : configurableFields.values()) {
String envValue = System.getenv(field.getEnvName(true));
if (envValue != null) {
field.setStringValue(envValue);
} else if ((envValue = System.getenv(field.getEnvName(false))) != null) {
field.setStringValue(envValue);
}
}
}
/**
* Sets the class field values that are set in the specified in the file found at the
* propertiesFilePath
*
* @param propertiesFilePath
* @return
*/
private Properties loadProperties(String propertiesFilePath) {
Properties properties = readPropertiesFile(propertiesFilePath);
applyProperties(properties);
return properties;
}
/**
* Sets the class field values that are set in the specified in the file found at the
* propertiesFilePath
*
* @param propertiesFilePath
*/
private void setApiVersion(String propertiesFilePath) {
Properties properties = readPropertiesFile(propertiesFilePath);
apiVersion = properties.getProperty("semantic.versioning.api.version");
}
/**
* Sets the class field values that are set in the supplied properties object
*
* @param properties
*/
private void applyProperties(Properties properties) {
if (properties.size() == 0) {
return;
}
for (ConfigDescriptor field : configurableFields.values()) {
String propertyValue = properties.getProperty(field.getPropertyName());
if (propertyValue != null) {
field.setStringValue(propertyValue);
}
}
}
/**
* Reads the file located at the specified file path into a Properties object
*
* @param filepath
* @return
*/
private Properties readPropertiesFile(String filepath) {
Properties properties = new Properties();
try {
if (filepath != null && !filepath.isBlank()) {
if (filepath.contains("!/")) {
properties.load(this.getClass().getResourceAsStream(filepath.split("!")[1]));
} else {
properties.load(new FileInputStream(filepath));
}
}
} catch (Exception e) {
logger.error("Unable to read properties file at: {}", filepath, e);
}
return properties;
}
public String getFeatureBranchIdentifier() {
String value = configurableFields.get("featureBranchIdentifier").getStringValue();
return value;
}
public void setFeatureBranchIdentifier(String value) {
configurableFields.get("featureBranchIdentifier").setStringValue(value);
}
public String getSemanticSeparator() {
return configurableFields.get("semanticSeparator").getStringValue();
}
public String getDetailsSeparator() {
return configurableFields.get("detailsSeparator").getStringValue();
}
public String getReleaseFormat() {
return configurableFields.get("releaseFormat").getStringValue();
}
public String getSnapshotFormat() {
return configurableFields.get("snapshotFormat").getStringValue();
}
public int getBranchTrimLength() {
return configurableFields.get("branchTrimLength").getIntValue();
}
public String getMasterFormat() {
return configurableFields.get("masterFormat").getStringValue();
}
public String getJavaArtifactSnapshotFormat() {
return configurableFields.get("javaArtifactSnapshotFormat").getStringValue();
}
public String getJavaArtifactReleaseFormat() {
return configurableFields.get("javaArtifactReleaseFormat").getStringValue();
}
public void setJavaArtifactReleaseFormat(String javaArtifactReleaseFormat) {
configurableFields.get("javaArtifactReleaseFormat").setStringValue(javaArtifactReleaseFormat);
}
public String getArchiveArtifactSnapshotFormat() {
return configurableFields.get("archiveArtifactSnapshotFormat").getStringValue();
}
public String getArchiveArtifactReleaseFormat() {
return configurableFields.get("archiveArtifactReleaseFormat").getStringValue();
}
public String getDefaultVersionFormatter() {
return configurableFields.get("defaultVersionFormatter").getStringValue();
}
public void setDefaultVersionFormatter(String defaultVersionFormatter) {
configurableFields.get("defaultVersionFormatter").setStringValue(defaultVersionFormatter);
}
public String getDefaultVersionParser() {
return configurableFields.get("defaultVersionParser").getStringValue();
}
public void setDefaultVersionParser(String defaultVersionParser) {
configurableFields.get("defaultVersionParser").setStringValue(defaultVersionParser);
}
public String getMasterBranchName() {
return configurableFields.get("masterBranchName").getStringValue();
}
public String getReleaseBranchName() {
return configurableFields.get("releaseBranchName").getStringValue();
}
public static String getApiVersion() {
return apiVersion;
}
public String getWorkingDirectory() {
return configurableFields.get("workingDirectory").getStringValue();
}
public void setWorkingDirectory(String workingDirectory) {
configurableFields.get("workingDirectory").setStringValue(workingDirectory);
}
public int getBuildNumberPadLength() {
return configurableFields.get("buildNumberPadLength").getIntValue();
}
public Properties getRuntimeProperties() {
return runtimeProperties;
}
}