ezbake.common.properties.EzProperties.java Source code

Java tutorial

Introduction

Here is the source code for ezbake.common.properties.EzProperties.java

Source

/*   Copyright (C) 2013-2014 Computer Sciences Corporation
 *
 * 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 ezbake.common.properties;

import com.google.common.collect.Sets;
import com.google.common.io.Closeables;

import ezbake.common.io.ClasspathResources;
import ezbake.common.security.TextCryptoProvider;
import ezbake.common.openshift.OpenShiftUtil;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.math.NumberUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class extends {@link java.util.Properties} to add convenience methods to get primitives besides strings, deal with
 * encrypted values, and load from various resources including streams
 *
 * If you are going to be dealing with encrypted properties then you are going to have to make sure to the set the
 * TextCryptoProvider eg:
 *
 *      EzProperties ezProperties = new EzProperties();
 *      ezProperties.setTextEncryptor(new SharedSecretTextCryptoProvider(System.getenv("SHARED_SECRET")));
 *
 */
public class EzProperties extends Properties {

    private TextCryptoProvider cryptoProvider = null;

    private static Logger logger = LoggerFactory.getLogger(EzProperties.class);

    public EzProperties() {
        this(null, false);
    }

    /**
     * Constructor which takes in properties and will either use the properties objects as "defaults" like the
     * java.util.Properties constructor or if copyProperties is true, then we do a putAll and have all the properties
     * internally.
     *
     * @param props properties which we want to either copy or use as defaults
     * @param copyProperties  is a flag for whether we want to copy the properties into this object or use the
     * properties as a "default" (keep a reference to the object and if we can't find it in our properties object then
     * we go look in the "default" properties object)
     */
    public EzProperties(Properties props, boolean copyProperties) {
        if (!copyProperties) {
            this.defaults = props;
        } else {
            putAll(props);
        }
    }

    /**
     * Set the crypto provider to use when encrypting and decrypting values
     *
     * @param provider the text crypto provider to use {@see ezbake.common.security.TextCryptoProvider}
     */
    public synchronized void setTextCryptoProvider(TextCryptoProvider provider) {
        this.cryptoProvider = provider;
    }

    /**
     * Get the crypt provider that this object is using to encrypt and decrypt values
     *
     * @return {@see ezbake.common.security.TextCryptoProvider} a text crypto provider
     */
    public TextCryptoProvider getTextCryptoProvider() {
        return cryptoProvider;
    }

    /**
     * Obtains the property value for the specified key {@see java.util.Properties.getProperty(java.lang.String)},
     * decrypting it if needed.
     *
     * @param key the property key
     *
     * @throws IllegalStateException if the TextCryptoProvider is null and has not been initialized if trying to read a
     * encrypted property
     * @throws SecurityExcepton if there is a problem decrypting the value
     *
     * @return the decrypted value
     */
    @Override
    public String getProperty(String key) throws SecurityException, IllegalArgumentException {
        return decrypt(super.getProperty(key));
    }

    /**
     * Obtains the property value for the specified key {@see java.util.Properties.getProperty(java.lang.String)},
     * decrypting it if needed.
     *
     * @param key the property key
     * @param defaultValue the default value to return
     *
     * @throws IllegalStateException if the TextCryptoProvider is null and has not been initialized if trying to read a
     * encrypted property
     * @throws SecurityExcepton if there is a problem decrypting the value
     * @return the decrypted value
     */
    @Override
    public String getProperty(String key, String defaultValue) throws SecurityException, IllegalStateException {
        return decrypt(super.getProperty(key, defaultValue));
    }

    /**
     * Set a property {@see java.util.Properties#setProperty(java.lang.String, java.lang.String)}
     *
      * @param key the key to be placed in this properties list
      * @param value the value corresponding to the key
      * @param isEncrypted whether or not we should encrypt the property
      *
      * @throws IllegalStateException if the TextCryptoProvider is null and has not been initialized if trying to read a
      * encrypted property
      * @throws SecurityExceptoin if there is a problem encrypting
      *
      * @return the previous value of the specified key in this property list, or null if it did not have one.
      */
    public void setProperty(String key, String value, boolean isEncrypted) throws SecurityException {
        if (isEncrypted) {
            if (cryptoProvider == null) {
                throw new IllegalStateException("Crypto provider has not been initialized!");
            }
            value = PropertiesEncryptionUtil.encryptPropertyValue(value, cryptoProvider);
        }

        super.setProperty(key, value);
    }

    /**
     * Get a property as a boolean, if the property doesn't exist or is not the string "true" this will return false
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value doesn't equal "true"
     *
     * @return true if the key has a value of "true" it will return "true" if the key has a value of "false" it will
     * return false, else it will return the default value
     */
    public boolean getBoolean(String propertyName, boolean defaultValue) {
        String value = getProperty(propertyName);
        if (value == null) {
            return defaultValue;
        }

        value = value.trim().toLowerCase(); // normalize our string, get rid of whitespace and lower case
        boolean retVal = defaultValue;
        if (value.equals("true")) {
            retVal = true;
        } else if (value.equals("false")) {
            retVal = false;
        }

        return retVal;
    }

    /**
     * Get a property as a double, if the property doesn't exist or can't be converted then we return the default value
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value can't be converted to a double
     *
     * @return either the properly parsed double or the default value if the key doesn't exist or can't be converted
     */
    public double getDouble(String propertyName, double defaultValue) {
        return NumberUtils.toDouble(getProperty(propertyName), defaultValue);
    }

    /**
     * Get a property as a float, if the property doesn't exist or can't be converted then we return the default value
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value can't be converted to a float
     *
     * @return either the properly parsed float or the default value if the key doesn't exist or can't be converted
     */
    public float getFloat(String propertyName, float defaultValue) {
        return NumberUtils.toFloat(getProperty(propertyName), defaultValue);
    }

    /**
     * Get a property as a int, if the property doesn't exist or can't be converted then we return the default value
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value can't be converted to a int
     *
     * @return either the properly parsed int or the default value if the key doesn't exist or can't be converted
     */
    public int getInteger(String propertyName, int defaultValue) {
        return NumberUtils.toInt(getProperty(propertyName), defaultValue);
    }

    /**
     * Get a property as a long, if the property doesn't exist or can't be converted then we return the default value
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value can't be converted to a long
     *
     * @return either the properly parsed long or the default value if the key doesn't exist or can't be converted
     */
    public long getLong(String propertyName, long defaultValue) {
        return NumberUtils.toLong(getProperty(propertyName), defaultValue);
    }

    /**
     * Get a property as a path, if this doesn't exist throw an IOException
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue the value to return if the key doesn't exist
     *
     * @return the proper path or null if we can't find the directory
     */
    public String getPath(String propertyName, String defaultValue) {
        String path = getProperty(propertyName);
        if (path == null) {
            return defaultValue;
        }

        if (!OpenShiftUtil.inOpenShiftContainer()) {
            return path;
        }

        File file = new File(path);
        if (file.isAbsolute()) {
            return path;
        }

        /*
         * OpenShift cartridges like JBoss don't play nice with relative paths to match thrift runner we assume that we
         * are using relative paths.
         */
        return OpenShiftUtil.getRepoDir() + File.separator + path;

    }

    /**
     * Get a property as a short, if the property doesn't exist or can't be converted then we return the default value
     *
     * @param propertyName is the name of the property we are looking for (the key)
     * @param defaultValue is the value to return if the key doesn't exist or the value can't be converted to a short
     *
     * @return either the properly parsed short or the default value if the key doesn't exist or can't be converted
     */
    public short getShort(String propertyName, short defaultValue) {
        return NumberUtils.toShort(getProperty(propertyName), defaultValue);
    }

    /**
     * Check to see what properties would "collide" (have the same key name)
     *
     * @param toCheck are the properties that we want to compare our properties with
     *
     * @return a set of string which are they keys that overlap
     */
    public Set<String> getCollisions(Properties toCheck) {
        Set<String> commonValues = Sets.intersection(stringPropertyNames(), toCheck.stringPropertyNames())
                .immutableCopy();
        return commonValues;
    }

    /**
     * Merge one set of properties with our properties.  It does this using a putAll so we copy the properties from
     * toMerge into our properties
     *
     * @param toMerge are the properties that we want to merge into our own
     * @param shouldOverride whether or not we should just override, if false then we will check for collisions
     *
     * @throws DuplicatePropertiesException if we are checking for collisions and we actually have collisions this will
     * contain all the "keys" which collide.
     */
    public void mergeProperties(Properties toMerge, boolean shouldOverride) throws DuplicatePropertyException {
        if (!shouldOverride) {
            Set<String> collisions = getCollisions(toMerge);
            if (!collisions.isEmpty()) {
                throw new DuplicatePropertyException(collisions);
            }
        }
        putAll(toMerge);
    }

    /**
     * This will glob properties files form a directory and merge them into our properties files.
     * {@see  mergeProperties(Properties, boolean)}
     *
     * @param directory the path of the directory to read from
     * @param globPattern a pattern of wild card characters for the filenames to match against
     * @param shouldOverride whether or not we should just override, if false then we will check for collisions
     *
     * @throws IOException if there are any problems with talking to the file system or if the directory doesn't exist
     * @throws DuplicatePropertyException if we are checking for collisions and we actually have collisions this will
     * contain all the "keys" which collide.
     */
    public void loadFromDirectory(String directory, String globPattern, boolean shouldOverride)
            throws IOException, DuplicatePropertyException {
        loadFromDirectory(Paths.get(directory), globPattern, shouldOverride);
    }

    /**
     * This will glob properties files form a directory and merge them into our properties files.
     * {@see  mergeProperties(Properties, boolean)}
     *
     * @param directory the path of the directory to read from
     * @param globPattern a pattern of wild card characters for the filenames to match against
     * @param shouldOverride whether or not we should just override, if false then we will check for collisions
     *
     * @throws DuplicatePropertyException if we are checking for collisions and we actually have collisions this will
     * contain all the "keys" which collide.
     * @throws IllegalArgumentException if the directory was null or is not a directory
     * @throws IOException if there are any problems with talking to the file system or if the directory doesn't exist
     */
    public void loadFromDirectory(Path directory, String globPattern, boolean shouldOverride)
            throws IOException, DuplicatePropertyException {
        if (directory == null || !Files.isDirectory(directory)) {
            String dir = (directory == null) ? "null" : directory.toString();
            throw new IllegalArgumentException("The directory " + dir + " is null or does not exist!");
        }

        DirectoryStream<Path> ds = null;
        try {
            ds = Files.newDirectoryStream(directory, globPattern);
            for (Path p : ds) {
                Properties pro = new Properties();
                File propFile = p.toFile();
                if (propFile.canRead()) {
                    pro.load(new FileReader(propFile));
                } else {

                    // This is expected to happen sometimes with our
                    // configuration, but it's probably helpful to
                    // know, so let's stick with info-level here.

                    logger.info("I am skipping the properties file {} "
                            + "because I do not have permission to read it.", p);

                }
                mergeProperties(pro, shouldOverride);
            }
        } finally {
            if (ds != null) {
                Closeables.close(ds, true);
            }
        }
    }

    /**
     * Loads properties from the class path.
     * @{see mergeProperties(Properties, boolean)
     *
     * @param resourceToLoad the class path resource to load
     * @param shouldOverride whether or not we should just override, if false then we will check for collisions
     *
     * @throws DuplicatePropertyException if we are checking for collisions and we actually have collisions this will
     * contain all the "keys" which collide.
     * @throws IllegalArgumentException if the class path was null or if we couldn't find it on the class path
     * @throws IOException if there are any problems with loading the file from the class path
     */
    public void loadFromClassPath(String resource, boolean shouldOverride)
            throws IOException, DuplicatePropertyException {
        URL resourceUrl = ClasspathResources.getResource(resource);
        if (resourceUrl == null) {
            throw new IllegalArgumentException(
                    resource + " is either or null or does not exist on the class path!");
        }

        Properties toMerge = new Properties();
        toMerge.load(resourceUrl.openStream());
        mergeProperties(toMerge, shouldOverride);
    }

    private String decrypt(String value) {
        if (!PropertiesEncryptionUtil.isEncryptedValue(value)) {
            return value;
        }

        if (cryptoProvider == null) {
            throw new IllegalStateException("Crypto provider has not been initialized!");
        }

        return PropertiesEncryptionUtil.decryptPropertyValue(value, cryptoProvider);
    }
}