/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.platform.util.properties;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.support.PropertiesLoaderUtils;
/**
* Utility class that attempts several different methods for loading in properties files from the
* classpath or file system. The strategies are attempted in the following order:
* <ol>
* <li>Spring (default class loader) uses utilities found in Spring-Core to load properties.</li>
* <li>Spring (given class loader) does the same as above, but with respect to the given class loader.</li>
* <li>Direct file system loading; useful when the properties have a fully-qualified absolute path.
* Relative paths are also valid, and loading attempts path resolution using (in order):
* <ol>
* <li>The relative path prepended with the {@code karaf.home} property</li>
* <li>The relative path prepended with the {@code ddf.home} property</li>
* <li>The relative path itself, without any modification</li>
* </ol>
* </li>
* <li>Resource loading using Java's built in resource system</li>
* </ol>
* Note that the first successful strategy is the one whose results are non-empty, and no further strategies
* will be attempted thereafter. Property placeholders are always substituted after the loading has finished.
* <p>
* If all strategies fail, then the returned Properties object will be empty.
*/
public final class PropertiesLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesLoader.class);
private static final PropertiesLoader INSTANCE = new PropertiesLoader();
private PropertiesLoader() {
}
public static PropertiesLoader getInstance() {
return INSTANCE;
}
/**
* Converts an instance of Properties to an instance of a Map, which is the preferred API for
* working with key-value collections.
*
* @param properties the properties object whose elements should be in the resultant map
* @param <K> the object type of the key in the returned map
* @param <V> the object type of the value in the returned map
* @return a new map with all elements of the given properties, or empty if the properties were null
*/
@SuppressWarnings("unchecked")
public <K, V> Map<K, V> toMap(Properties properties) {
if (properties != null) {
final Set<Map.Entry<Object, Object>> entries = properties.entrySet();
Map<K, V> map = new HashMap<K, V>(entries.size() * 2);
for (Map.Entry<Object, Object> entry : entries) {
map.put((K) entry.getKey(), (V) entry.getValue());
}
return map;
}
return new HashMap<K, V>();
}
/**
* Load properties from a file with no classloader specified.
*
* @param propertiesFile the resource name or the file path of the properties file
* @return Properties deserialized from the specified file, or empty if the load failed
*/
public Properties loadProperties(String propertiesFile) {
return loadProperties(propertiesFile, null);
}
/**
* Will attempt to load properties from a file using the given classloader. If that fails,
* several other methods will be tried until the properties file is located.
*
* @param propertiesFile the resource name or the file path of the properties file
* @param classLoader the class loader with access to the properties file
* @return Properties deserialized from the specified file, or empty if the load failed
*/
public Properties loadProperties(String propertiesFile, ClassLoader classLoader) {
boolean error = false;
Properties properties = new Properties();
if (propertiesFile != null) {
try {
LOGGER.debug(
"Attempting to load properties from {} with Spring PropertiesLoaderUtils.",
propertiesFile);
properties = PropertiesLoaderUtils.loadAllProperties(propertiesFile);
} catch (IOException e) {
error = true;
LOGGER.debug("Unable to load properties using default Spring properties loader.",
e);
}
if (error || properties.isEmpty()) {
if (classLoader != null) {
try {
LOGGER.debug(
"Attempting to load properties from {} with Spring PropertiesLoaderUtils with class loader.",
propertiesFile);
properties = PropertiesLoaderUtils.loadAllProperties(propertiesFile,
classLoader);
error = false;
} catch (IOException e) {
error = true;
LOGGER.debug(
"Unable to load properties using default Spring properties loader.",
e);
}
} else {
try {
LOGGER.debug(
"Attempting to load properties from {} with Spring PropertiesLoaderUtils with class loader.",
propertiesFile);
properties = PropertiesLoaderUtils.loadAllProperties(propertiesFile,
PropertiesLoader.class.getClassLoader());
error = false;
} catch (IOException e) {
error = true;
LOGGER.debug(
"Unable to load properties using default Spring properties loader.",
e);
}
}
}
if (error || properties.isEmpty()) {
LOGGER.debug("Attempting to load properties from file system: {}", propertiesFile);
File propFile = new File(propertiesFile);
// If properties file has fully-qualified absolute path (which
// the blueprint file specifies) then can load it directly.
if (propFile.isAbsolute()) {
LOGGER.debug("propertiesFile {} is absolute", propertiesFile);
propFile = new File(propertiesFile);
} else {
String rootDirectory = System.getProperty("karaf.home");
if (rootDirectory != null && !rootDirectory.isEmpty()) {
propFile = new File(rootDirectory, propertiesFile);
} else {
rootDirectory = System.getProperty("ddf.home");
if (rootDirectory != null && !rootDirectory.isEmpty()) {
propFile = new File(rootDirectory, propertiesFile);
} else {
propFile = new File(propertiesFile);
}
}
}
properties = new Properties();
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(propFile),
StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (FileNotFoundException e) {
error = true;
LOGGER.debug("Could not find properties file: {}",
propFile.getAbsolutePath(),
e);
} catch (IOException e) {
error = true;
LOGGER.debug("Error reading properties file: {}",
propFile.getAbsolutePath(),
e);
}
}
if (error || properties.isEmpty()) {
LOGGER.debug("Attempting to load properties as a resource: {}", propertiesFile);
InputStream ins = PropertiesLoader.class.getResourceAsStream(propertiesFile);
if (ins != null) {
try {
properties.load(ins);
ins.close();
} catch (IOException e) {
LOGGER.debug("Unable to load properties: {}", propertiesFile, e);
} finally {
IOUtils.closeQuietly(ins);
}
}
}
//replace any ${prop} with system properties
Properties filtered = new Properties();
for (Map.Entry<?, ?> entry : properties.entrySet()) {
filtered.put(StrSubstitutor.replaceSystemProperties(entry.getKey()),
StrSubstitutor.replaceSystemProperties(entry.getValue()));
}
properties = filtered;
} else {
LOGGER.debug("Properties file must not be null.");
}
return properties;
}
}