/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.springframework.context.support; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.jasypt.properties.EncryptableProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.core.io.Resource; import org.springframework.util.CollectionUtils; /** * Custom extension of {@link PropertySourcesPlaceholderConfigurer} that serves three purposes: * * <ul> * <li>Override postProcessing to provide access to "local" properties before bean post-processing * has completed * <li>Force configuration setting ignoreResourceNotFound=true and (safely) ignore noisy WARNings * in the log concerning missing properties files that are optional * <li>Provide support for encrypted property values based on Jasypt * </ul> * */ public class PortalPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { public static final String EXTENDED_PROPERTIES_SOURCE = "extendedPropertiesSource"; public static final String JAYSYPT_ENCRYPTION_KEY_VARIABLE = "UP_JASYPT_KEY"; private PropertyResolver propertyResolver; private final Logger logger = LoggerFactory.getLogger(getClass()); public PortalPropertySourcesPlaceholderConfigurer() { /* * We rely on this config for our optional properties files */ super.setIgnoreResourceNotFound(true); } @Override public void setIgnoreResourceNotFound(boolean value) { if (value == false) { final String msg = "Instances of PortalPropertySourcesPlaceholderConfigurer " + "are always ignoreResourceNotFound=true"; throw new UnsupportedOperationException(msg); } } /** * uPortal defines some properties files in its primaryPropertyPlaceholderConfigurer bean that * are considered (and documented) optional. The parent class * (PropertySourcesPlaceholderConfigurer) will operate properly without them, but puts a * significant number of WARN messages into the log. These are noisy, and could lead a new * adopter to think that something's wrong. This method removes absent properties files from the * collection. */ @Override public void setLocations(Resource[] locations) { final List<Resource> list = new ArrayList<>(); for (Resource r : locations) { if (r.exists()) { list.add(r); } else { // In our case this event is worth a DEBUG note. logger.debug( "The following Resource was not present (it may be " + "optional, or it's absence may lead to issues): ", r); } } super.setLocations(list.toArray(new Resource[0])); } /** * Override the postProcessing. The default PropertySourcesPlaceholderConfigurer does not inject * local properties into the Environment object. It builds a local list of properties files and * then uses a transient Resolver to resolve the @Value annotations. That means that you are * unable to get to "local" properties (eg. portal.properties) after bean post-processing has * completed unless you are going to re-parse those file. This is similar to what * PropertiesManager does, but it uses all the property files configured, not just * portal.properties. * * <p>If we upgrade to spring 4, there are better/more efficient solutions available. I'm not * aware of better solutions for spring 3.x. * * @param beanFactory the bean factory * @throws BeansException if an error occurs while loading properties or wiring up beans */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (propertyResolver == null) { try { MutablePropertySources sources = new MutablePropertySources(); PropertySource<?> localPropertySource = new PropertiesPropertySource(EXTENDED_PROPERTIES_SOURCE, mergeProperties()); sources.addLast(localPropertySource); propertyResolver = new PropertySourcesPropertyResolver(sources); } catch (IOException e) { throw new BeanInitializationException("Could not load properties", e); } } super.postProcessBeanFactory(beanFactory); } /** * Get a property resolver that can read local properties. * * @return a property resolver that can be used to dynamically read the merged property values * configured in applicationContext.xml */ public PropertyResolver getPropertyResolver() { return propertyResolver; } /** * Override PropertiesLoaderSupport.mergeProprties in order to slip in a properly-configured * EncryptableProperties instance, allowing us to encrypt property values at rest. */ @Override protected Properties mergeProperties() throws IOException { Properties rslt = null; /* * If properties file encryption is used in this deployment, the * encryption key will be made available to the application as an * environment variable called UP_JASYPT_KEY. */ final String encryptionKey = System.getenv(JAYSYPT_ENCRYPTION_KEY_VARIABLE); if (encryptionKey != null) { logger.info("Jasypt support for encrypted property values ENABLED"); StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword(encryptionKey); rslt = new EncryptableProperties(encryptor); /* * BEGIN copied from PropertiesLoaderSupport.mergeProperties() */ if (this.localOverride) { // Load properties from file upfront, to let local properties override. loadProperties(rslt); } if (this.localProperties != null) { for (int i = 0; i < this.localProperties.length; i++) { CollectionUtils.mergePropertiesIntoMap(this.localProperties[i], rslt); } } if (!this.localOverride) { // Load properties from file afterwards, to let those properties override. loadProperties(rslt); } /* * END copied from PropertiesLoaderSupport.mergeProperties() */ } else { logger.info( "Jasypt support for encrypted property values DISABLED; " + "specify environment variable {} to use this feature", JAYSYPT_ENCRYPTION_KEY_VARIABLE); /* * The feature is not in use; defer to the Spring-provided * implementation of this method. */ return super.mergeProperties(); } return rslt; } }