com.brienwheeler.lib.spring.beans.PropertyPlaceholderConfigurer.java Source code

Java tutorial

Introduction

Here is the source code for com.brienwheeler.lib.spring.beans.PropertyPlaceholderConfigurer.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Brien L. Wheeler (brienwheeler@yahoo.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.brienwheeler.lib.spring.beans;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PropertyPlaceholderHelper;

/**
 * This drop-in replacement for the Spring PropertyPlaceholderConfigurer
 * facilitates property inheritance from parent contexts to children contexts by
 * setting resolved property values as System properties (if not already set)
 * and always using mode SYSTEM_PROPERTIES_MODE_OVERRIDE.
 * <p>
 * It also facilitates the use of many granular property files in a context by
 * merging all property file contents within a single context together and
 * processing them all at once, so inter-file dependencies are easily handled.
 * 
 * @author Brien Wheeler
 */
public class PropertyPlaceholderConfigurer
        extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer {
    private static final Log log = LogFactory.getLog(PropertyPlaceholderConfigurer.class);

    private static final Map<ConfigurableListableBeanFactory, ContextData> contextDataMap = new HashMap<ConfigurableListableBeanFactory, ContextData>();
    private static final String OBFUSCATED_LOG_VALUE = "********";

    private PropertyPlaceholderOrder placeholderOrder;
    private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
    private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
    private boolean quiet = false;

    public PropertyPlaceholderConfigurer() {
        super.setSystemPropertiesMode(SYSTEM_PROPERTIES_MODE_OVERRIDE);
    }

    @Override
    public void setPlaceholderPrefix(String placeholderPrefix) {
        super.setPlaceholderPrefix(placeholderPrefix);
        this.placeholderPrefix = placeholderPrefix;
    }

    @Override
    public void setPlaceholderSuffix(String placeholderSuffix) {
        super.setPlaceholderSuffix(placeholderSuffix);
        this.placeholderSuffix = placeholderSuffix;
    }

    @Override
    public int getOrder() {
        return placeholderOrder != null ? placeholderOrder.getOrder() : super.getOrder();
    }

    public void setPlaceholderOrder(PropertyPlaceholderOrder placeholderOrder) {
        this.placeholderOrder = placeholderOrder;
    }

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties properties)
            throws BeansException {
        ContextData contextData;
        synchronized (contextDataMap) {
            contextData = contextDataMap.get(beanFactoryToProcess);
            if (contextData == null) {
                contextData = new ContextData();
                contextDataMap.put(beanFactoryToProcess, contextData);
            }
        }

        int ppcCount = beanFactoryToProcess.getBeanNamesForType(PropertyPlaceholderConfigurer.class, true,
                false).length;
        synchronized (contextData) {
            /*
             * Spring calls all placeholder configurers according to their Order value.
             * We want to aggregate all placeholder definitions at the same Order value and
             * resolve them at the sane time, to ease inter-file dependency management.
             */

            /*
             * First we check to see if we have previously aggregated properties for a
             * different order value.  If so, resolve the previously aggregated properties
             * and clear out the aggregation before proceeding. 
             */
            Integer previousOrder = contextData.isOrderChanging(getOrder());
            if (previousOrder != null) {
                if (!quiet)
                    log.info("Processing merged context properties of order " + previousOrder);
                processProperties(contextData.getProperties(), placeholderPrefix, placeholderSuffix);
                contextData.clearProperties();
            }

            /*
             * Now we just merge these definitions into the current Order level aggregation.
             * We'll process them when we hit another placeholder configurer with a different
             * Order value, or when we hit the last placeholder configurer in the context.
             */
            CollectionUtils.mergePropertiesIntoMap(properties, contextData.getProperties());

            /*
             * Finally, if this is the last placeholder configurer in the context we need
             * to resolve the aggregated properties and call Spring to process these into
             * bean defintions, et al.
             */
            if (contextData.incrementPpcCount() == ppcCount) {
                if (!quiet)
                    log.info("Processing merged context properties of order " + getOrder());
                processProperties(contextData.getProperties(), placeholderPrefix, placeholderSuffix);
                contextData.clearProperties();
                log.info("Resolving context placeholders");
                super.processProperties(beanFactoryToProcess, contextData.getProperties());
            }
        }
    }

    /**
      * Resolves any placeholders in the supplied properties map, preferring the use of
      * previously set System properties over the current property map contents for
      * placeholder substitution.
      * <p>
      * Also sets any resolved properties as System properties, if no System
      * property by that name already exists.
      * 
      * @param properties the merged context properties
      */
    public static void processProperties(final Properties properties) {
        processProperties(properties, DEFAULT_PLACEHOLDER_PREFIX, DEFAULT_PLACEHOLDER_SUFFIX);
    }

    /**
      * Resolves any placeholders in the supplied properties map, preferring the use of
      * previously set System properties over the current property map contents for
      * placeholder substitution.
      * <p>
      * Also sets any resolved properties as System properties, if no System
      * property by that name already exists.
      * 
      * @param properties the merged context properties
      */
    public static void processProperties(final Properties properties, String placeholderPrefix,
            String placeholderSuffix) {
        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix);
        PropertyPlaceholderHelper.PlaceholderResolver resolver = new PropertyPlaceholderHelper.PlaceholderResolver() {
            @Override
            public String resolvePlaceholder(String placeholderName) {
                // SYSTEM_PROPERTIES_MODE_OVERRIDE means we look at previously set
                // system properties in preference to properties defined in our file
                String value = System.getProperty(placeholderName);
                if (value == null)
                    value = properties.getProperty(placeholderName);
                return value;
            }
        };

        for (Object key : properties.keySet()) {
            String propertyName = (String) key;
            // get the value from the map
            String propertyValue = properties.getProperty(propertyName);
            // resolve it against system properties then other properties in the
            // passed-in Properties
            String resolvedValue = helper.replacePlaceholders(propertyValue, resolver);

            // set back into passed-in Properties if changed
            if (!ObjectUtils.nullSafeEquals(propertyValue, resolvedValue)) {
                properties.setProperty(propertyName, resolvedValue);
            }

            // set into System properties if not already set
            setProperty(propertyName, resolvedValue);
        }
    }

    public static void setProperty(String propertyName, String propertyValue) {
        // set into System properties if not already set
        if (System.getProperty(propertyName) == null) {
            if (log.isInfoEnabled()) {
                String logValue = propertyValue;
                if (propertyName.toLowerCase().contains("password"))
                    logValue = OBFUSCATED_LOG_VALUE;
                if (propertyName.toLowerCase().contains("secret"))
                    logValue = OBFUSCATED_LOG_VALUE;
                log.info("Setting system property: " + propertyName + "=" + logValue);
            }
            System.setProperty(propertyName, propertyValue);
        } else {
            if (log.isDebugEnabled())
                log.debug("Skipping system property: " + propertyName + " (already set)");
        }
    }

    /**
     * Programatically process a single property file location, using System
     * properties in preference to included definitions for placeholder
     * resolution, and setting any resolved properties as System properties, if
     * no property by that name already exists.
     * 
     * @param locationName String-encoded resource location for the properties file to process
     */
    public static void processLocation(String locationName) {
        processLocation(locationName, null, null);
    }

    /**
     * Programatically process a single property file location, using System
     * properties in preference to included definitions for placeholder
     * resolution, and setting any resolved properties as System properties, if
     * no property by that name already exists.
     * 
     * @param locationName String-encoded resource location for the properties file to process
     * @param placeholderPrefix the placeholder prefix String
     * @param placeholderSuffix the placeholder suffix String
     */
    public static void processLocation(String locationName, String placeholderPrefix, String placeholderSuffix) {
        ResourceEditor resourceEditor = new ResourceEditor();
        resourceEditor.setAsText(locationName);

        PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
        configurer.setLocation((Resource) resourceEditor.getValue());
        if (placeholderPrefix != null)
            configurer.setPlaceholderPrefix(placeholderPrefix);
        if (placeholderSuffix != null)
            configurer.setPlaceholderSuffix(placeholderSuffix);
        configurer.quiet = true;

        GenericApplicationContext context = new GenericApplicationContext();
        context.getBeanFactory().registerSingleton("propertyPlaceholderConfigurer", configurer);
        context.refresh();
        context.close();
    }

    /**
     * Helper class to track aggregated property data and current Order level of the 
     * placeholder processing.
     *  
     * @author Brien Wheeler
     */
    private static class ContextData {
        private int ppcCount;
        private Integer currentOrder;
        private final Properties properties = new Properties();

        Properties getProperties() {
            return properties;
        }

        void clearProperties() {
            properties.clear();
        }

        synchronized int incrementPpcCount() {
            return ++ppcCount;
        }

        Integer isOrderChanging(int nextOrder) {
            Integer previousOrder = null;
            if ((currentOrder == null) || (nextOrder != currentOrder.intValue())) {
                previousOrder = currentOrder;
                currentOrder = nextOrder;
            }
            return previousOrder;
        }
    }
}