/**
* Copyright (C) 2015 Orange
* 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 com.francetelecom.clara.cloud.commons;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.Constants;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Properties;
/**
* A jndi-aware extension of the Spring PropertyPlaceholderConfigurer (v2.5.6).
* By default this resolves properties from jndi first, then any referenced
* property files and then falls back to System properties and the System
* Environment.
* <p>
* It also performs property expansion on values passed in as Locations in case
* the resource locations themselves have placeHolder values that need to be
* resolved from jndi or system properties
* </p>
*
* <p>
* An example usage in which we have two property files referenced via a
* configDirectory parameter injected as a jndi or system property
* </p>
*
* <pre>
* <!--
* Expose jndi, system and config properties to bean definitions. This expects a jndi or system
* property configDirectory to our directory of configuration files
* -->
* <bean
* id="propertyPlaceholderConfigurer"
* class="org.springframework.beans.factory.config.JndiAwarePropertyPlaceholderConfigurer"
* init-method="initialize">
* <property
* name="locations">
* <list>
* <value>file:${configDirectory}/../common.properties
* </value>
* <value>file:${configDirectory}/application.properties
* </value>
* </list>
* </property>
* </bean>
* </pre>
*
* @Author arthur.branham@morganstanley.com
* @see <a href="arthur.branham%2540morganstanley.com">Spring ticket SPR-3030 from Arthur</a>
*/
public class JndiAwarePropertyPlaceholderConfigurer
extends PropertyPlaceholderConfigurer {
private static final Constants pConstants = new Constants(PropertyPlaceholderConfigurer.class);
/**
* by default we will search for jndi values and if found these will
* override any other values
*/
private int jndiPropertiesMode = SYSTEM_PROPERTIES_MODE_OVERRIDE;
/**
* if searchJndiEnvironment is set to true then we will search the jndi
* environment for properties
*/
private boolean searchJndiEnvironment = true;
/**
* Set to true to expect the "java:comp/env/" prefix
*/
private boolean resourceRef = false;
/**
* Takes an array or Resources and for any of type UrlResource, resolves any
* properties in the URL. Note that as we haven't loaded the property fiels
* at this stage we are resolving properties against the system and jndi
* sets (if any)
*
* @param locations
*/
@SuppressWarnings("unchecked")
private void processLocationValues(Resource[] locations) {
if (locations != null) {
Properties props = new Properties();
HashSet visitedPlaceholders = new HashSet();
for (int i = 0; i < locations.length; i++) {
if (locations[i] instanceof UrlResource) {
UrlResource file = (UrlResource) locations[i];
String path;
try {
path = file.getURL()
.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
String value = parseStringValue(path, props, visitedPlaceholders);
if (!StringUtils.equals(path, value)) {
UrlResource newFile;
try {
newFile = new UrlResource(value);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
locations[i] = newFile;
}
}
}
}
}
/**
* Resolve the given placeholder using the given properties. Default
* implementation simply checks for an environment entry for a corresponding
* property key.
* <p>
* Subclasses can override this for customized placeholder-to-key mappings
* or custom resolution strategies, possibly just using the given lookup as
* a fallback.
*
* @param placeholder
* the placeholder to resolve
* @return the resolved value, of <code>null</code> if none
*/
protected String resolveJndiProperty(String placeholder) {
InitialContext initialContext = null;
try {
initialContext = new InitialContext();
try {
String prefix;
if (resourceRef) {
prefix = "java:comp/env/";
} else {
prefix = "";
}
return (String) initialContext.lookup(prefix + placeholder);
} catch (NameNotFoundException e) {
return null;
} catch (NamingException e) {
return null;
}
} catch (NamingException e) {
throw new RuntimeException(e);
} finally {
if (initialContext != null) {
try {
initialContext.close();
} catch (NamingException e) {
}
}
}
}
/**
* Override of PropertyPlaceholderConfigurer.resolvePlaceholder to handle
* jndi property lookup.
*
* <p>
* Warning: note that we are directly accessing the instance variable
* jndiPropertiesMode rather than accepting it as an input parameter (simply
* to avoid having to rewrite the calling method)
* </p>
* <p>
* The overrid/fallback mode of jndi properties relative to property-file
* values can be controlled by settign the jndiPropertiesMode property but
* the behaviour relative to system properties is hardcoded below. ie In
* both override and fallback mode a jndi property beats a system property.
*/
@Override
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (searchJndiEnvironment && this.jndiPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
// in override mode a jndi property can override a system property
propVal = resolveJndiProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && searchJndiEnvironment && jndiPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
// in fallback mode a jndi property takes precedence over a system
// property
propVal = resolveJndiProperty(placeholder);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
/**
* Set how to check jndi properties: as fallback, as override, or never. For
* example, will resolve ${user.dir} to the "user.dir" jndi property.
* <p>
* The default is "override": jndi always wins
*
* @see #SYSTEM_PROPERTIES_MODE_NEVER
* @see #SYSTEM_PROPERTIES_MODE_FALLBACK
* @see #SYSTEM_PROPERTIES_MODE_OVERRIDE
* @see #setJndiPropertiesModeName
*/
public void setJndiPropertiesMode(int jndiPropertiesMode) {
this.jndiPropertiesMode = jndiPropertiesMode;
}
/**
* Set the jndi property mode by the name of the corresponding constant,
* e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
*
* @param constantName
* name of the constant
* @throws java.lang.IllegalArgumentException
* if an invalid constant was specified
* @see #setJndiPropertiesMode
*/
public void setJndiPropertiesModeName(String constantName) throws IllegalArgumentException {
this.jndiPropertiesMode = pConstants.asNumber(constantName)
.intValue();
}
/**
* Set a location of a properties file to be loaded.
* <p>
* Can point to a classic properties file or to an XML file that follows JDK
* 1.5's properties XML format.
*/
@Override
public void setLocation(Resource location) {
this.tempLocations = new Resource[] { location };
}
private Resource[] tempLocations;
/**
* Set locations of properties files to be loaded.
* <p>
* Can point to classic properties files or to XML files that follow JDK
* 1.5's properties XML format.
* <p>
* Note: Properties defined in later files will override properties defined
* earlier files, in case of overlapping keys. Hence, make sure that the
* most specific files are the last ones in the given list of locations.
*/
@Override
public void setLocations(Resource[] locations) {
tempLocations = locations;
}
public void setSearchJndiEnvironment(boolean searchJndiEnvironment) {
this.searchJndiEnvironment = searchJndiEnvironment;
}
/**
* resolve locations and let our superclass know about them
*/
public void initialize() {
processLocationValues(this.tempLocations);
super.setLocations(tempLocations);
}
public boolean isResourceRef() {
return resourceRef;
}
public void setResourceRef(boolean resourceRef) {
this.resourceRef = resourceRef;
}
}