/** * This file Copyright (c) 2008-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.cms.beans.config; import info.magnolia.cms.core.Path; import info.magnolia.cms.core.SystemProperty; import info.magnolia.module.ModuleRegistry; import info.magnolia.module.model.ModuleDefinition; import info.magnolia.module.model.PropertyDefinition; import info.magnolia.objectfactory.Components; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import javax.inject.Inject; import javax.servlet.ServletContext; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for loading the various "magnolia.properties" files, merging them, * and substituting variables in their values. * @author pbracher * @author fgiust * * @deprecated since 4.5 - replaced by classes in the {@link info.magnolia.init} package. */ public class PropertiesInitializer { private static final Logger log = LoggerFactory.getLogger(PropertiesInitializer.class); /** * The properties file containing the bean default implementations. */ private static final String MGNL_BEANS_PROPERTIES = "/mgnl-beans.properties"; /** * Placeholder prefix: "${". */ public static final String PLACEHOLDER_PREFIX = "${"; /** * Placeholder suffix: "}". */ public static final String PLACEHOLDER_SUFFIX = "}"; /** * Context attribute prefix, to obtain a property definition like ${contextAttribute/property}, that can refer to * any context attribute. */ public static final String CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX = "contextAttribute/"; //$NON-NLS-1$ /** * Context parameter prefix, to obtain a property definition like ${contextParam/property}, that can refer to any * context parameter. */ public static final String CONTEXT_PARAM_PLACEHOLDER_PREFIX = "contextParam/"; //$NON-NLS-1$ /** * System property prefix, to obtain a property definition like ${systemProperty/property}, that can refer to any * System property. */ public static final String SYSTEM_PROPERTY_PLACEHOLDER_PREFIX = "systemProperty/"; //$NON-NLS-1$ /** * System property prefix, to obtain a property definition like ${systemProperty/property}, that can refer to any * System property. */ public static final String ENV_PROPERTY_PLACEHOLDER_PREFIX = "env/"; //$NON-NLS-1$ /** * @deprecated since 4.5, use IoC */ public static PropertiesInitializer getInstance() { return Components.getSingleton(PropertiesInitializer.class); } /** * Default value for the MAGNOLIA_INITIALIZATION_FILE parameter. */ public static final String DEFAULT_INITIALIZATION_PARAMETER = // "WEB-INF/config/${servername}/${webapp}/magnolia.properties," //$NON-NLS-1$ + "WEB-INF/config/${servername}/magnolia.properties," //$NON-NLS-1$ + "WEB-INF/config/${webapp}/magnolia.properties," //$NON-NLS-1$ + "WEB-INF/config/default/magnolia.properties," //$NON-NLS-1$ + "WEB-INF/config/magnolia.properties"; //$NON-NLS-1$ private final ModuleRegistry moduleRegistry; @Inject public PropertiesInitializer(ModuleRegistry moduleRegistry) { this.moduleRegistry = moduleRegistry; } public void loadAllProperties(String propertiesFilesString, String rootPath) { // load mgnl-beans.properties first loadBeanProperties(); loadAllModuleProperties(); // complete or override with WEB-INF properties files loadPropertiesFiles(propertiesFilesString, rootPath); // complete or override with JVM system properties overloadWithSystemProperties(); // resolve nested properties resolveNestedProperties(); } private void resolveNestedProperties() { Properties sysProps = SystemProperty.getProperties(); for (Iterator<Object> it = sysProps.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); String oldValue = (String) sysProps.get(key); String value = parseStringValue(oldValue, new HashSet<String>()); SystemProperty.getProperties().put(key, value.trim()); } } public void loadAllModuleProperties() { // complete or override with modules' properties final List<ModuleDefinition> moduleDefinitions = moduleRegistry.getModuleDefinitions(); loadModuleProperties(moduleDefinitions); } /** * Load the properties defined in the module descriptors. They can get overridden later in the properties files in * WEB-INF */ protected void loadModuleProperties(List<ModuleDefinition> moduleDefinitions) { for (ModuleDefinition module : moduleDefinitions) { for (PropertyDefinition property : module.getProperties()) { SystemProperty.setProperty(property.getName(), property.getValue()); } } } public void loadPropertiesFiles(String propertiesLocationString, String rootPath) { String[] propertiesLocation = StringUtils.split(propertiesLocationString, ','); boolean found = false; // attempt to load each properties file at the given locations in reverse order: first files in the list // override the later ones for (int j = propertiesLocation.length - 1; j >= 0; j--) { String location = StringUtils.trim(propertiesLocation[j]); if (loadPropertiesFile(rootPath, location)) { found = true; } } if (!found) { final String msg = MessageFormat.format("No configuration found using location list {0}. Base path is [{1}]", ArrayUtils.toString(propertiesLocation), rootPath); //$NON-NLS-1$ log.error(msg); throw new ConfigurationException(msg); } } /** * @deprecated since 4.5, replaced by a new ClasspathPropertySource("/mgnl-beans.properties"). * @see info.magnolia.init.DefaultMagnoliaConfigurationProperties */ public void loadBeanProperties() { InputStream mgnlbeansStream = getClass().getResourceAsStream(MGNL_BEANS_PROPERTIES); if (mgnlbeansStream != null) { Properties mgnlbeans = new Properties(); try { mgnlbeans.load(mgnlbeansStream); } catch (IOException e) { log.error("Unable to load {} due to an IOException: {}", MGNL_BEANS_PROPERTIES, e.getMessage()); } finally { IOUtils.closeQuietly(mgnlbeansStream); } for (Iterator<Object> iter = mgnlbeans.keySet().iterator(); iter.hasNext();) { String key = (String) iter.next(); SystemProperty.setProperty(key, mgnlbeans.getProperty(key)); } } else { log.warn("{} not found in the classpath. Check that all the needed implementation classes are defined in your custom magnolia.properties file.", MGNL_BEANS_PROPERTIES); } } /** * Try to load a magnolia.properties file. * @param rootPath * @param location * @return */ public boolean loadPropertiesFile(String rootPath, String location) { final File initFile; if (Path.isAbsolute(location)) { initFile = new File(location); } else { initFile = new File(rootPath, location); } if (!initFile.exists() || initFile.isDirectory()) { log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath()); //$NON-NLS-1$ return false; } InputStream fileStream = null; try { fileStream = new FileInputStream(initFile); } catch (FileNotFoundException e1) { log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath()); return false; } try { SystemProperty.getProperties().load(fileStream); log.info("Loading configuration at {}", initFile.getAbsolutePath());//$NON-NLS-1$ } catch (Exception e) { log.error(e.getMessage(), e); return false; } finally { IOUtils.closeQuietly(fileStream); } return true; } /** * Overload the properties with set system properties. */ public void overloadWithSystemProperties() { Iterator<Object> it = SystemProperty.getProperties().keySet().iterator(); while (it.hasNext()) { String key = (String) it.next(); if (System.getProperties().containsKey(key)) { log.info("system property found: {}", key); String value = System.getProperty(key); SystemProperty.setProperty(key, value); } } } /** * Returns the property files configuration string passed, replacing all the needed values: ${servername} will be * reaplced with the name of the server, ${webapp} will be replaced with webapp name, ${systemProperty/*} will be * replaced with the corresponding system property, ${env/*} will be replaced with the corresponding environment property, * and ${contextAttribute/*} and ${contextParam/*} will be replaced with the corresponding attributes or parameters * taken from servlet context. * This can be very useful for all those application servers that has multiple instances on the same server, like * WebSphere. Typical usage in this case: * <code>WEB-INF/config/${servername}/${contextAttribute/com.ibm.websphere.servlet.application.host}/magnolia.properties</code> * * @param context Servlet context * @param servername Server name * @param webapp Webapp name * @param propertiesFilesString a comma separated list of paths. * @return Property file configuration string with everything replaced. * * @deprecated since 4.5, this is done by {@link info.magnolia.init.DefaultMagnoliaPropertiesResolver#DefaultMagnoliaPropertiesResolver}. * Note: when remove this class and method, this code will need to be cleaned up and moved to info.magnolia.init.DefaultMagnoliaPropertiesResolver */ public static String processPropertyFilesString(ServletContext context, String servername, String webapp, String propertiesFilesString, String contextPath) { // Replacing basic properties. propertiesFilesString = StringUtils.replace(propertiesFilesString, "${servername}", servername); //$NON-NLS-1$ propertiesFilesString = StringUtils.replace(propertiesFilesString, "${webapp}", webapp); //$NON-NLS-1$ propertiesFilesString = StringUtils.replace(propertiesFilesString, "${contextPath}", contextPath); // Replacing servlet context attributes (${contextAttribute/something}) String[] contextAttributeNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX); if (contextAttributeNames != null) { for (String ctxAttrName : contextAttributeNames) { if (ctxAttrName != null) { // Some implementation may not accept a null as attribute key, but all should accept an empty // string. final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX + ctxAttrName + PLACEHOLDER_SUFFIX; final Object attrValue = context.getAttribute(ctxAttrName); if (attrValue != null) { propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, attrValue.toString()); } } } } // Replacing servlet context parameters (${contextParam/something}) String[] contextParamNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_PARAM_PLACEHOLDER_PREFIX); if (contextParamNames != null) { for (String ctxParamName : contextParamNames) { if (ctxParamName != null) { // Some implementation may not accept a null as param key, but an empty string? TODO Check. final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_PARAM_PLACEHOLDER_PREFIX + ctxParamName + PLACEHOLDER_SUFFIX; final String paramValue = context.getInitParameter(ctxParamName); if (paramValue != null) { propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue); } } } } // Replacing system property (${systemProperty/something}) String[] systemPropertiesNames = getNamesBetweenPlaceholders(propertiesFilesString, SYSTEM_PROPERTY_PLACEHOLDER_PREFIX); if (systemPropertiesNames != null) { for (String sysPropName : systemPropertiesNames) { if (StringUtils.isNotBlank(sysPropName)) { final String originalPlaceHolder = PLACEHOLDER_PREFIX + SYSTEM_PROPERTY_PLACEHOLDER_PREFIX + sysPropName + PLACEHOLDER_SUFFIX; final String paramValue = System.getProperty(sysPropName); if (paramValue != null) { propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue); } } } } // Replacing environment property (${env/something}) String[] envPropertiesNames = getNamesBetweenPlaceholders(propertiesFilesString, ENV_PROPERTY_PLACEHOLDER_PREFIX); if (envPropertiesNames != null) { for (String envPropName : envPropertiesNames) { if (StringUtils.isNotBlank(envPropName)) { final String originalPlaceHolder = PLACEHOLDER_PREFIX + ENV_PROPERTY_PLACEHOLDER_PREFIX + envPropName + PLACEHOLDER_SUFFIX; final String paramValue = System.getenv(envPropName); if (paramValue != null) { propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue); } } } } return propertiesFilesString; } private static String[] getNamesBetweenPlaceholders(String propertiesFilesString, String contextNamePlaceHolder) { final String[] names = StringUtils.substringsBetween( propertiesFilesString, PLACEHOLDER_PREFIX + contextNamePlaceHolder, PLACEHOLDER_SUFFIX); return StringUtils.stripAll(names); } /** * Parse the given String value recursively, to be able to resolve nested placeholders. Partly borrowed from * org.springframework.beans.factory.config.PropertyPlaceholderConfigurer (original author: Juergen Hoeller) * * @deprecated since 4.5 this is now done by {@link info.magnolia.init.AbstractMagnoliaConfigurationProperties#parseStringValue}. */ protected String parseStringValue(String strVal, Set<String> visitedPlaceholders) { StringBuffer buf = new StringBuffer(strVal); int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); while (startIndex != -1) { int endIndex = -1; int index = startIndex + PLACEHOLDER_PREFIX.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { if (PLACEHOLDER_SUFFIX.equals(buf.subSequence(index, index + PLACEHOLDER_SUFFIX.length()))) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + PLACEHOLDER_SUFFIX.length(); } else { endIndex = index; break; } } else if (PLACEHOLDER_PREFIX.equals(buf.subSequence(index, index + PLACEHOLDER_PREFIX.length()))) { withinNestedPlaceholder++; index = index + PLACEHOLDER_PREFIX.length(); } else { index++; } } if (endIndex != -1) { String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); if (!visitedPlaceholders.add(placeholder)) { log.warn("Circular reference detected in properties, \"{}\" is not resolvable", strVal); return strVal; } // Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = SystemProperty.getProperty(placeholder); if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, visitedPlaceholders); buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal); startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length()); } else { // Proceed with unprocessed value. startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length()); } visitedPlaceholders.remove(placeholder); } else { startIndex = -1; } } return buf.toString(); } }