All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ro.pippo.core.PippoSettings Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
/*
 * Copyright (C) 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 ro.pippo.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.util.ClasspathUtils;
import ro.pippo.core.util.StringUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

/**
 * A simple properties-file based settings class for Pippo applications.
 * 

* The properties file is automatically loaded from one of the following * sources: *

    *
  1. from the -Dpippo.settings system property *
  2. from the current working directory (application.properties) *
  3. from the application classpath (conf/application.properties) *
  4. using the built-in Pippo properties *
*

*

* A settings file located on the filesystem is automatically reloaded, if * modified, on the next setting lookup. *

*

* All settings support runtime-mode configuration allowing you to specify the * same setting with mode-dependent values. *

*

* Settings without a mode prefix are the shared for all modes unless explicitly * overridden by a mode-specific setting. *

*

*

 * example.value = my example value
 * %prod.example.value = my production value
 * %dev.example.value = my development value
 * 
*

* Setting files support including/importing settings from other setting files * using the include= setting. The include= setting is a * comma-delimited field allowing importing settings from multiple sources. * Inclusions may be recursive. *

* * @author James Moger */ public class PippoSettings { private static final Logger log = LoggerFactory.getLogger(PippoSettings.class); private static final String USING_DEFAULT_OF = " using default of "; private static final String DEFAULT_LIST_DELIMITER = ","; private final RuntimeMode runtimeMode; private final Properties properties; private final Properties overrides; private final Map interpolationValues; private final URL propertiesUrl; private final boolean isFile; private volatile long lastModified; public PippoSettings(RuntimeMode runtimeMode) { this.runtimeMode = runtimeMode; this.propertiesUrl = getPropertiesUrl(); this.isFile = propertiesUrl.getProtocol().equals("file:"); this.interpolationValues = new HashMap<>(); loadInterpolationValues(); this.properties = loadProperties(propertiesUrl); this.overrides = new Properties(); } public RuntimeMode getRuntimeMode() { return runtimeMode; } public boolean isDev() { return RuntimeMode.DEV == runtimeMode; } public boolean isTest() { return RuntimeMode.TEST == runtimeMode; } public boolean isProd() { return RuntimeMode.PROD == runtimeMode; } public String getLocalHostname() { // try InetAddress.LocalHost first; // NOTE -- InetAddress.getLocalHost().getHostName() will not work in // certain environments. try { String result = InetAddress.getLocalHost().getHostName(); if (!StringUtils.isNullOrEmpty(result)) return result; } catch (UnknownHostException e) { // failed; try alternate means. } // try environment properties. // String host = System.getenv("COMPUTERNAME"); if (host != null) return host; host = System.getenv("HOSTNAME"); if (host != null) return host; // undetermined. return "Pippo"; } private URL getPropertiesUrl() { URL url = null; try { // System property String systemProperty = System.getProperty(PippoConstants.SYSTEM_PROPERTY_PIPPO_SETTINGS, null); if (!StringUtils.isNullOrEmpty(systemProperty)) { url = URI.create(systemProperty).toURL(); log.debug("Located '{}' using the '{}' system property", url, PippoConstants.SYSTEM_PROPERTY_PIPPO_SETTINGS); } } catch (MalformedURLException e) { log.error("Failed to parse '{}' system property!", PippoConstants.SYSTEM_PROPERTY_PIPPO_SETTINGS); } try { // working directory Path path = Paths.get(System.getProperty("user.dir"), PippoConstants.APPLICATION_PROPERTIES); if (path.toFile().exists()) { url = path.toUri().toURL(); log.debug("Located '{}' in the current working directory", url); } } catch (MalformedURLException e) { log.error("Failed to parse working directory properties!", e); } if (url == null) { // try locating an application classpath properties file url = ClasspathUtils.locateOnClasspath(PippoConstants.LOCATION_OF_PIPPO_CLASSPATH_PROPERTIES); } if (url == null) { // last resort, use built-in properties url = ClasspathUtils.locateOnClasspath(PippoConstants.LOCATION_OF_PIPPO_BUILTIN_PROPERTIES); } return url; } private synchronized Properties getProperties() { if (isFile) { File file = new File(propertiesUrl.getPath()); if (file.lastModified() > lastModified) { Properties reloadedProperties = loadProperties(propertiesUrl); properties.clear(); properties.putAll(reloadedProperties); lastModified = file.lastModified(); } } return properties; } private synchronized Properties loadProperties(URL propertiesUrl) { final Properties properties = new Properties(); try (InputStream is = propertiesUrl.openStream()) { log.debug("loading {}", propertiesUrl); Properties props = new Properties(); props.load(is); File baseDir; if (isFile) { baseDir = new File(propertiesUrl.getPath()).getParentFile(); } else { // use current working directory for includes/overrides baseDir = new File(System.getProperty("user.dir")); } // allow including other properties files props = loadIncludes(baseDir, props); // allow overriding properties props = loadOverrides(baseDir, props); // collect settings for the current runtime mode String prefix = "%" + runtimeMode.toString() + "."; // sort names, mode-specific names are first Set names = new TreeSet<>(props.stringPropertyNames()); for (String name : names) { if (name.charAt(0) == '%') { // mode-specific setting if (name.startsWith(prefix)) { // setting for the current runtime mode properties.put(name.substring(prefix.length()), props.get(name)); } } else if (!properties.containsKey(name)) { // setting for all modes properties.put(name, props.get(name)); } } } catch (FileNotFoundException f) { throw new PippoRuntimeException(f, "Failed to find {}", propertiesUrl); } catch (Exception t) { throw new PippoRuntimeException(t, "Failed to read {}", propertiesUrl); } return properties; } /** * Recursively read "include" properties files. *

* "Include" properties are the base properties which are overwritten by * the provided properties. *

* * @param baseDir * @param properties * @return the merged properties * @throws IOException */ private Properties loadIncludes(File baseDir, Properties properties) throws IOException { return loadProperties(baseDir, properties, "include", false); } /** * Recursively read "override" properties files. *

* "Override" properties overwrite the provided properties. *

* * @param baseDir * @param properties * @return the merged properties * @throws IOException */ private Properties loadOverrides(File baseDir, Properties properties) throws IOException { return loadProperties(baseDir, properties, "override", true); } /** * Recursively read referenced properties files. * * @param baseDir * @param currentProperties * @param key * @param override * @return the merged properties * @throws IOException */ private Properties loadProperties(File baseDir, Properties currentProperties, String key, boolean override) throws IOException { Properties loadedProperties = new Properties(); String include = (String) currentProperties.remove(key); if (!StringUtils.isNullOrEmpty(include)) { // allow for multiples List names = StringUtils.getList(include, DEFAULT_LIST_DELIMITER); for (String name : names) { if (StringUtils.isNullOrEmpty(name)) { continue; } // interpolate any variables final String fileName = interpolateString(name); // try co-located File file = new File(baseDir, fileName); if (!file.exists()) { // try absolute path file = new File(fileName); } if (!file.exists()) { log.warn("failed to locate {}", file); continue; } // load properties log.debug("loading {} settings from {}", key, file); try (FileInputStream iis = new FileInputStream(file)) { loadedProperties.load(iis); } // read nested properties loadedProperties = loadProperties(file.getParentFile(), loadedProperties, key, override); } } Properties merged = new Properties(); if (override) { // "override" settings overwrite the current properties merged.putAll(currentProperties); merged.putAll(loadedProperties); } else { // "include" settings are overwritten by the current properties merged.putAll(loadedProperties); merged.putAll(currentProperties); } return merged; } /** * Creates a map of prepared interpolation values. *

* All values support the standard ${name} syntax but also support * the @{name} syntax to cope with build systems with aggressive * token filtering. *

*/ protected void loadInterpolationValues() { // System properties may be accessed using ${name} or @{name} for (String propertyName : System.getProperties().stringPropertyNames()) { String value = System.getProperty(propertyName); if (!StringUtils.isNullOrEmpty(value)) { addInterpolationValue(propertyName, value); } } // Environment variables may be accessed using ${env.NAME} or @{env.NAME} for (String variableName : System.getenv().keySet()) { String value = System.getenv(variableName); if (!StringUtils.isNullOrEmpty(value)) { addInterpolationValue("env." + variableName, value); } } } /** * Add a value that may be interpolated. * * @param name * @param value */ protected void addInterpolationValue(String name, String value) { interpolationValues.put(String.format("${%s}", name), value); interpolationValues.put(String.format("@{%s}", name), value); } /** * Interpolates a string value using System properties and Environment variables. * * @param value * @return an interpolated string */ protected String interpolateString(String value) { String interpolatedValue = value; for (Map.Entry entry : interpolationValues.entrySet()) { interpolatedValue = interpolatedValue.replace(entry.getKey(), entry.getValue()); } return interpolatedValue; } /** * Returns the list of all setting names. * * @return list of setting names */ public List getSettingNames() { return getSettingNames(null); } /** * Returns the list of settings whose name starts with the specified prefix. If * the prefix is null or empty, all settings names are returned. * * @param startingWith * @return list of setting names */ public List getSettingNames(String startingWith) { List names = new ArrayList<>(); Properties props = getProperties(); if (StringUtils.isNullOrEmpty(startingWith)) { names.addAll(props.stringPropertyNames()); } else { startingWith = startingWith.toLowerCase(); for (Object o : props.keySet()) { String name = o.toString(); if (name.toLowerCase().startsWith(startingWith)) { names.add(name); } } } return names; } /** * Returns the string value for the specified name. If the name does not exist * or the value for the name can not be interpreted as a string, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public String getString(String name, String defaultValue) { String value = getProperties().getProperty(name, defaultValue); value = overrides.getProperty(name, value); return value; } /** * Returns a string value that has been interpolated from System Properties * and Environment Variables using the ${name} or @{name} syntax. * * @param name * @param defaultValue * @return an interpolated string */ public String getInterpolatedString(String name, String defaultValue) { String value = getString(name, defaultValue); return interpolateString(value); } /** * Returns the boolean value for the specified name. If the name does not * exist or the value for the name can not be interpreted as a boolean, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public boolean getBoolean(String name, boolean defaultValue) { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return Boolean.parseBoolean(value.trim()); } return defaultValue; } /** * Returns the integer value for the specified name. If the name does not * exist or the value for the name can not be interpreted as an integer, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public int getInteger(String name, int defaultValue) { try { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return Integer.parseInt(value.trim().split(" ")[0]); } } catch (NumberFormatException e) { log.warn("Failed to parse integer for " + name + USING_DEFAULT_OF + defaultValue); } return defaultValue; } /** * Returns the long value for the specified name. If the name does not * exist or the value for the name can not be interpreted as an long, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public long getLong(String name, long defaultValue) { try { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return Long.parseLong(value.trim().split(" ")[0]); } } catch (NumberFormatException e) { log.warn("Failed to parse long for " + name + USING_DEFAULT_OF + defaultValue); } return defaultValue; } /** * Returns the float value for the specified name. If the name does not * exist or the value for the name can not be interpreted as a float, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public float getFloat(String name, float defaultValue) { try { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return Float.parseFloat(value.trim().split(" ")[0]); } } catch (NumberFormatException e) { log.warn("Failed to parse float for " + name + USING_DEFAULT_OF + defaultValue); } return defaultValue; } /** * Returns the double value for the specified name. If the name does not * exist or the value for the name can not be interpreted as a double, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public double getDouble(String name, double defaultValue) { try { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return Double.parseDouble(value.trim().split(" ")[0]); } } catch (NumberFormatException e) { log.warn("Failed to parse double for " + name + USING_DEFAULT_OF + defaultValue); } return defaultValue; } /** * Returns the char value for the specified name. If the name does not exist * or the value for the name can not be interpreted as a char, the * defaultValue is returned. * * @param name * @param defaultValue * @return name value or defaultValue */ public char getChar(String name, char defaultValue) { String value = getString(name, null); if (!StringUtils.isNullOrEmpty(value)) { return value.trim().charAt(0); } return defaultValue; } /** * Returns the string value for the specified name. If the name does not * exist an exception is thrown. * * @param name * @return name value */ public String getRequiredString(String name) { String value = getString(name, null); if (value != null) { return value.trim(); } throw new PippoRuntimeException("Setting '{}' has not been configured!", name); } /** * Returns a list of comma-delimited strings from the specified name. * * @param name * @return list of strings */ public List getStrings(String name) { return getStrings(name, DEFAULT_LIST_DELIMITER); } /** * Returns a list of strings from the specified name using the specified delimiter. * * @param name * @param delimiter * @return list of strings */ public List getStrings(String name, String delimiter) { String value = getString(name, null); if (StringUtils.isNullOrEmpty(value)) { return Collections.emptyList(); } return StringUtils.getList(value, delimiter); } /** * Returns a list of comma-delimited integers from the specified name. * * @param name * @return list of strings */ public List getIntegers(String name) { return getIntegers(name, DEFAULT_LIST_DELIMITER); } /** * Returns a list of integers from the specified name using the specified delimiter. * * @param name * @param delimiter * @return list of integers */ public List getIntegers(String name, String delimiter) { List strings = getStrings(name, delimiter); if (strings.isEmpty()) { return Collections.emptyList(); } List ints = new ArrayList<>(); for (String value : strings) { try { int i = Integer.parseInt(value); ints.add(i); } catch (NumberFormatException e) { } } return Collections.unmodifiableList(ints); } /** * Returns a list of comma-delimited longs from the specified name. * * @param name * @return list of strings */ public List getLongs(String name) { return getLongs(name, DEFAULT_LIST_DELIMITER); } /** * Returns a list of longs from the specified name using the specified delimiter. * * @param name * @param delimiter * @return list of longs */ public List getLongs(String name, String delimiter) { List strings = getStrings(name, delimiter); if (strings.isEmpty()) { return Collections.emptyList(); } List longs = new ArrayList<>(); for (String value : strings) { try { long i = Long.parseLong(value); longs.add(i); } catch (NumberFormatException e) { } } return Collections.unmodifiableList(longs); } /** * Gets the duration setting and converts it to milliseconds. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @return milliseconds */ public long getDurationInMilliseconds(String name) { return getDurationInMilliseconds(name, 0); } /** * Gets the duration setting and converts it to milliseconds. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @param defaultValue in milliseconds * @return milliseconds */ public long getDurationInMilliseconds(String name, long defaultValue) { TimeUnit timeUnit = extractTimeUnit(name, defaultValue + " MILLISECONDS"); long duration = getLong(name, defaultValue); return timeUnit.toMillis(duration); } /** * Gets the duration setting and converts it to seconds. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @return seconds */ public long getDurationInSeconds(String name) { return getDurationInSeconds(name, 0); } /** * Gets the duration setting and converts it to seconds. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @param defaultValue in seconds * @return seconds */ public long getDurationInSeconds(String name, long defaultValue) { TimeUnit timeUnit = extractTimeUnit(name, defaultValue + " SECONDS"); long duration = getLong(name, defaultValue); return timeUnit.toSeconds(duration); } /** * Gets the duration setting and converts it to minutes. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @return minutes */ public long getDurationInMinutes(String name) { return getDurationInMinutes(name, 0); } /** * Gets the duration setting and converts it to minutes. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @param defaultValue in minutes * @return minutes */ public long getDurationInMinutes(String name, long defaultValue) { TimeUnit timeUnit = extractTimeUnit(name, defaultValue + " MINUTES"); long duration = getLong(name, defaultValue); return timeUnit.toMinutes(duration); } /** * Gets the duration setting and converts it to hours. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @return hours */ public long getDurationInHours(String name) { return getDurationInHours(name, 0); } /** * Gets the duration setting and converts it to hours. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @param defaultValue in hours * @return hours */ public long getDurationInHours(String name, long defaultValue) { TimeUnit timeUnit = extractTimeUnit(name, defaultValue + " HOURS"); long duration = getLong(name, defaultValue); return timeUnit.toHours(duration); } /** * Gets the duration setting and converts it to days. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @return days */ public long getDurationInDays(String name) { return getDurationInDays(name, 0); } /** * Gets the duration setting and converts it to days. *

* The setting must be use one of the following conventions: *

    *
  • n MILLISECONDS *
  • n SECONDS *
  • n MINUTES *
  • n HOURS *
  • n DAYS *
* * @param name * @param defaultValue in days * @return days */ public long getDurationInDays(String name, long defaultValue) { TimeUnit timeUnit = extractTimeUnit(name, defaultValue + " DAYS"); long duration = getLong(name, defaultValue); return timeUnit.toDays(duration); } /** * Extracts the TimeUnit from the name. * * @param name * @param defaultValue * @return the extracted TimeUnit */ private TimeUnit extractTimeUnit(String name, String defaultValue) { String value = getString(name, defaultValue); try { final String[] s = value.split(" ", 2); return TimeUnit.valueOf(s[1].trim().toUpperCase()); } catch (Exception e) { throw new PippoRuntimeException("{} must have format ' ' where is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'", name); } } /** * Tests for the existence of a setting. * * @param name * @return true if the setting exists */ public boolean hasSetting(String name) { return getString(name, null) != null; } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, boolean value) { overrides.put(name, Boolean.toString(value)); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, String value) { overrides.put(name, value); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, char value) { overrides.put(name, Character.toString(value)); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, int value) { overrides.put(name, Integer.toString(value)); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, long value) { overrides.put(name, Long.toString(value)); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, float value) { overrides.put(name, Float.toString(value)); } /** * Override the setting at runtime with the specified value. * This change does not persist. * * @param name * @param value */ public void overrideSetting(String name, double value) { overrides.put(name, Double.toString(value)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy