/* * Copyright (c) 2011 Lockheed Martin 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 org.eurekastreams.server.service.utility; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.jasypt.encryption.StringEncryptor; import org.jasypt.properties.PropertyValueEncryptionUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; /** * Derivation of Spring's class that loads property files and substitutes their values into the Spring config files. We * want access to the merged set of properties that Spring used so we can use them in the application (e.g. in * notification templates), but Spring does not provide access it. Hence this class, which captures Properties object * and provides a method to get it. * * This class is inherently tied to the implementation of Spring. It was based on version 2.5.6. * * There are two features which are not supported by the Properties object provided: 1. Access to values in the servlet * context. 2. Property references in property definitions (recursion). As such, looking up a property in the returned * Properties object may result in a different value than what would have been substituted into the Spring configuration * file. The reason is that the Spring classes do not perform a simple lookup on the Properties object. * * This class also provides a Map that allows property lookups without the above limitations. It has higher overhead, * and ONLY supports the get() method, but provides full compatibility with Spring (servlet access and recursive * definitions). */ public class PropertyExposingServletContextPropertyPlaceholderConfigurer extends org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer { /** Decryptor for handling encrypted configuration. */ private final StringEncryptor stringEncryptor; /** * Constructor. * * @param inStringEncryptor * Decryptor for handling encrypted configuration. */ public PropertyExposingServletContextPropertyPlaceholderConfigurer(final StringEncryptor inStringEncryptor) { stringEncryptor = inStringEncryptor; } // ---------- "APIs" - methods providing properties (accessed in Spring config) ---------- /** * @return The raw merged properties. */ public Properties getRawProperties() { return properties; } /** * @return Map allowing access to properties with full emulation of Spring-substituted values. */ public Map<String, String> getPropertyAccessor() { return new PropertyAccessor(); } // ---------- Support for encrypted properties ---------- // Uses Jasypt; code here based on Jasypt's EncryptablePropertyPlaceholderConfigurer and // ServletContextPropertyPlaceholderConfigurer. (Cannot use their classes directly since they were declared as // final.) /** * {@inheritDoc} */ @Override protected String convertPropertyValue(final String inOriginalValue) { return PropertyValueEncryptionUtils.isEncryptedValue(inOriginalValue) ? PropertyValueEncryptionUtils.decrypt( inOriginalValue, stringEncryptor) : inOriginalValue; } /** * {@inheritDoc} */ @Override protected String resolvePlaceholder(final String inPlaceholder, final Properties inProps) { return convertPropertyValue(super.resolvePlaceholder(inPlaceholder, inProps)); } // ---------- Supporting methods ---------- /** The merged set of properties. */ private Properties properties; /** * {@inheritDoc} */ @Override protected void processProperties(final ConfigurableListableBeanFactory inBeanFactoryToProcess, final Properties inProps) throws BeansException { // save off the properties properties = inProps; super.processProperties(inBeanFactoryToProcess, inProps); } // ---------- Supporting methods specific to property accessor map ---------- /** Value to replace with null. */ private String nullValue; /** Prefix which must be precede property names for parser. */ private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; /** Prefix which must be follow property names for parser. */ private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; /** * {@inheritDoc} */ @Override public void setNullValue(final String inNullValue) { nullValue = inNullValue; super.setNullValue(inNullValue); } /** * {@inheritDoc} */ @Override public void setPlaceholderPrefix(final String inPlaceholderPrefix) { placeholderPrefix = inPlaceholderPrefix; super.setPlaceholderPrefix(inPlaceholderPrefix); } /** * {@inheritDoc} */ @Override public void setPlaceholderSuffix(final String inPlaceholderSuffix) { placeholderSuffix = inPlaceholderSuffix; super.setPlaceholderSuffix(inPlaceholderSuffix); } /** * Map allowing access to properties with nested property support and servlet context access. */ private class PropertyAccessor implements Map<String, String> { /** Exception message for write operations. */ private static final String READ_ONLY_MSG = // \n "This map represents a merge of property files and a servlet context and is thus read-only."; /** Exception message for unsupported operations. */ private static final String POOR_CORRESPONDENCE_MSG = // \n "This map represents dynamically computed content, thus this operation does not apply."; /** * {@inheritDoc} */ @Override public int size() { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public boolean isEmpty() { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public boolean containsKey(final Object inKey) { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public boolean containsValue(final Object inValue) { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public String get(final Object inKey) { String key = placeholderPrefix + inKey.toString() + placeholderSuffix; // taken from PropertyPlaceholderConfigurer.PlaceholderResolvingStringValueResolver.resolveStringValue() String value = parseStringValue(key, properties, new HashSet()); return (value.equals(nullValue) ? null : value); } /** * {@inheritDoc} */ @Override public String put(final String inKey, final String inValue) { throw new UnsupportedOperationException(READ_ONLY_MSG); } /** * {@inheritDoc} */ @Override public String remove(final Object inKey) { throw new UnsupportedOperationException(READ_ONLY_MSG); } /** * {@inheritDoc} */ @Override public void putAll(final Map< ? extends String, ? extends String> inM) { throw new UnsupportedOperationException(READ_ONLY_MSG); } /** * {@inheritDoc} */ @Override public void clear() { throw new UnsupportedOperationException(READ_ONLY_MSG); } /** * {@inheritDoc} */ @Override public Set<String> keySet() { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public Collection<String> values() { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } /** * {@inheritDoc} */ @Override public Set<java.util.Map.Entry<String, String>> entrySet() { throw new UnsupportedOperationException(POOR_CORRESPONDENCE_MSG); } } }