/**
* Get more info at : www.jrebirth.org .
* Copyright JRebirth.org © 2011-2013
* Contact : sebastien.bordes@jrebirth.org
*
* 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.jrebirth.af.core.resource.parameter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jrebirth.af.api.log.JRLogger;
import org.jrebirth.af.api.resource.builder.ResourceBuilder;
import org.jrebirth.af.api.resource.parameter.ParameterItem;
import org.jrebirth.af.api.resource.parameter.ParameterParams;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.resource.ParameterEntry;
import org.jrebirth.af.core.resource.builder.AbstractResourceBuilder;
import org.jrebirth.af.core.util.ClasspathUtility;
/**
* The class <strong>ParameterBuilder</strong>.
*
* Class used to manage parameters with weak reference.
*
* @author Sébastien Bordes
*/
public final class ParameterBuilder extends AbstractResourceBuilder<ParameterItem<?>, ParameterParams, Object> implements ResourceBuilder<ParameterItem<?>, ParameterParams, Object>, ParameterMessages {
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(ParameterBuilder.class);
/** The pattern that matches Environment Variable ${varname} . */
private static final Pattern ENV_VAR_PATTERN1 = Pattern.compile("(.*)\\$\\{(\\w+)\\}(.*)");
/** The pattern that matches Environment Variable $varname . */
private static final Pattern ENV_VAR_PATTERN2 = Pattern.compile("(.*)\\$(\\w+)(.*)");
/** Store all parameter values defined into properties files. */
private final Map<String, ParameterEntry> propertiesParametersMap = new ConcurrentHashMap<>();
/** Store all overridden values defined by the call of define method. */
private final Map<ParameterItem<?>, Object> overriddenParametersMap = new ConcurrentHashMap<>();
/** Store all translated environment variable. */
private final Map<String, String> varenvMap = new HashMap<>();
/** The file extension used by configuration files. */
private String configurationFileExtension;
/** The Wildcard used to load configuration files. */
private String configurationFileWildcard;
/**
* Search configuration files according to the parameters provided.
*
* @param wildcard the regex wildcard (must not be null)
* @param extension the file extension without the first dot (ie: properties) (must not be null)
*/
public void searchConfigurationFiles(final String wildcard, final String extension) {
// Store parameters
this.configurationFileWildcard = wildcard;
this.configurationFileExtension = extension;
// Search and analyze all properties files available
readPropertiesFiles();
}
/**
* Read all configuration files available into the application classpath.
*/
private void readPropertiesFiles() {
if (this.configurationFileWildcard.isEmpty() || this.configurationFileExtension.isEmpty()) {
// Skip configuration loading
LOGGER.log(SKIP_CONF_LOADING);
} else {
// Assemble the regex pattern
final Pattern filePattern = Pattern.compile(this.configurationFileWildcard + "\\." + this.configurationFileExtension);
// Retrieve all resources from default classpath
final Collection<String> list = ClasspathUtility.getClasspathResources(filePattern);
LOGGER.log(CONFIG_FOUND, list.size(), list.size() > 1 ? "s" : "");
for (final String confFilename : list) {
readPropertiesFile(confFilename);
}
}
}
/**
* Read a customized configuration file to load parameters values.
*
* @param custConfFileName the file to load
*/
private void readPropertiesFile(final String custConfFileName) {
final Properties p = new Properties();
LOGGER.log(READ_CONF_FILE, custConfFileName);
try (InputStream is = ClasspathUtility.loadInputStream(custConfFileName)) {
// Read the properties file
p.load(is);
for (final Map.Entry<Object, Object> entry : p.entrySet()) {
if (this.propertiesParametersMap.containsKey(entry.getKey())) {
LOGGER.log(UPDATE_PARAMETER, entry.getKey(), entry.getValue());
} else {
LOGGER.log(STORE_PARAMETER, entry.getKey(), entry.getValue());
}
storePropertiesParameter(entry);
}
} catch (final IOException e) {
LOGGER.error(CONF_READING_ERROR, custConfFileName);
}
}
/**
* Store a parameter read from properties files.<br />
* The parameter is wrapped into a parameterEntry
*
* @param entry the entry to store
*/
private void storePropertiesParameter(final Map.Entry<Object, Object> entry) {
this.propertiesParametersMap.put(entry.getKey().toString(), new ParameterEntry(entry.getValue().toString()));
}
/**
* Resolve any environment variable found into the string.
*
* @param entryValue the string to check and resolve
*
* @return the final value with environment variable resolved
*/
private String resolveVarEnv(final String entryValue) {
String value = entryValue;
if (value != null) {
value = checkPattern(value, ENV_VAR_PATTERN1, true);
value = checkPattern(value, ENV_VAR_PATTERN2, false);
}
return value;
}
/**
* Check if the given string contains an environment variable.
*
* @param value the string value to parse
* @param pattern the regex pattern to use
* @param withBrace true for ${varname}, false for $varname
*
* @return the given string updated with right environment variable content
*/
private String checkPattern(final String value, final Pattern pattern, final boolean withBrace) {
String res = value;
final Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
final String envName = matcher.group(2);
if (!this.varenvMap.containsKey(envName)) {
final String envValue = System.getenv(envName);
this.varenvMap.put(envName, envValue);
}
// Check if the var env is ready
if (this.varenvMap.get(envName) != null) {
if (withBrace) {
res = res.replace("${" + envName + "}", this.varenvMap.get(envName));
} else {
res = res.replace("$" + envName, this.varenvMap.get(envName));
}
} else {
LOGGER.log(UNDEFINED_ENV_VAR, envName);
}
}
return res;
}
/**
* {@inheritDoc}
*/
@Override
protected Object buildResource(final ParameterItem<?> parameterItem, final ParameterParams parameterParams) {
Object object = null;
if (parameterParams instanceof ObjectParameter) {
final ObjectParameter<?> op = (ObjectParameter<?>) parameterParams;
// Load overridden values first
if (op.name() != null && this.overriddenParametersMap.containsKey(parameterItem)) {
// Retrieve the customized parameter
object = this.overriddenParametersMap.get(parameterItem);
}
// No overridden value is defined
// Check if the parameter has a parameter name and
// check if the parameter has been loaded from any customized configuration file
if (object == null && op.name() != null && this.propertiesParametersMap.containsKey(op.name())) {
// Retrieve the customized parameter
object = op.parseObject(this.propertiesParametersMap.get(op.name()));
}
// Object is still null
if (object == null) {
// No customized (properties and overridden) parameter has been loaded, gets the default programmatic one
object = op.object();
}
if (object instanceof String) {
object = resolveVarEnv((String) object);
}
// // Don't store the parameter into the map if it hasn't got any parameter name
// if (op.name() != null) {
//
// // Store the new parameter into the map
// this.propertiesParametersMap.put(op.name(), new ParameterEntry("", object));
// }
}
return object;
}
/**
* Override a parameter value.
*
* @param key the parameter item key
* @param forcedValue the overridden value
*/
public void define(final ParameterItem<?> key, final Object forcedValue) {
this.overriddenParametersMap.put(key, forcedValue);
set(getParamKey(key), forcedValue);
}
}