View Javadoc

1   /**
2    * This file Copyright (c) 2008-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.beans.config;
35  
36  import info.magnolia.cms.core.Path;
37  import info.magnolia.cms.core.SystemProperty;
38  import info.magnolia.module.ModuleRegistry;
39  import info.magnolia.module.model.ModuleDefinition;
40  import info.magnolia.module.model.PropertyDefinition;
41  import info.magnolia.objectfactory.Components;
42  
43  import java.io.File;
44  import java.io.FileInputStream;
45  import java.io.FileNotFoundException;
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.text.MessageFormat;
49  import java.util.HashSet;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Properties;
53  import java.util.Set;
54  
55  import javax.servlet.ServletContext;
56  
57  import org.apache.commons.io.IOUtils;
58  import org.apache.commons.lang.ArrayUtils;
59  import org.apache.commons.lang.StringUtils;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  /**
64   * This class is responsible for loading the various "magnolia.properties" files, merging them,
65   * and substituting variables in their values.
66   * @author pbracher
67   * @author fgiust
68   *
69   * @deprecated since 4.5 - replaced by classes in the {@link info.magnolia.init} package.
70   */
71  public class PropertiesInitializer {
72      private static final Logger log = LoggerFactory.getLogger(PropertiesInitializer.class);
73  
74      /**
75       * The properties file containing the bean default implementations.
76       */
77      private static final String MGNL_BEANS_PROPERTIES = "/mgnl-beans.properties";
78  
79      /**
80       * Placeholder prefix: "${".
81       */
82      public static final String PLACEHOLDER_PREFIX = "${";
83  
84      /**
85       * Placeholder suffix: "}".
86       */
87      public static final String PLACEHOLDER_SUFFIX = "}";
88  
89      /**
90       * Context attribute prefix, to obtain a property definition like ${contextAttribute/property}, that can refer to
91       * any context attribute.
92       */
93      public static final String CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX = "contextAttribute/"; //$NON-NLS-1$
94  
95      /**
96       * Context parameter prefix, to obtain a property definition like ${contextParam/property}, that can refer to any
97       * context parameter.
98       */
99      public static final String CONTEXT_PARAM_PLACEHOLDER_PREFIX = "contextParam/"; //$NON-NLS-1$
100 
101      /**
102       * System property prefix, to obtain a property definition like ${systemProperty/property}, that can refer to any
103       * System property.
104       */
105      public static final String SYSTEM_PROPERTY_PLACEHOLDER_PREFIX = "systemProperty/"; //$NON-NLS-1$
106 
107     /**
108      * System property prefix, to obtain a property definition like ${systemProperty/property}, that can refer to any
109      * System property.
110      */
111      public static final String ENV_PROPERTY_PLACEHOLDER_PREFIX = "env/"; //$NON-NLS-1$
112 
113     /**
114      * @deprecated since 4.5, use IoC
115      */
116     public static PropertiesInitializer getInstance() {
117         return Components.getSingleton(PropertiesInitializer.class);
118     }
119 
120     /**
121      * Default value for the MAGNOLIA_INITIALIZATION_FILE parameter.
122      */
123     public static final String DEFAULT_INITIALIZATION_PARAMETER = //
124     "WEB-INF/config/${servername}/${webapp}/magnolia.properties," //$NON-NLS-1$
125         + "WEB-INF/config/${servername}/magnolia.properties," //$NON-NLS-1$
126         + "WEB-INF/config/${webapp}/magnolia.properties," //$NON-NLS-1$
127         + "WEB-INF/config/default/magnolia.properties," //$NON-NLS-1$
128         + "WEB-INF/config/magnolia.properties"; //$NON-NLS-1$
129 
130     private final ModuleRegistry moduleRegistry;
131 
132     public PropertiesInitializer(ModuleRegistry moduleRegistry) {
133         this.moduleRegistry = moduleRegistry;
134     }
135 
136     public void loadAllProperties(String propertiesFilesString, String rootPath) {
137         // load mgnl-beans.properties first
138         loadBeanProperties();
139 
140         loadAllModuleProperties();
141 
142         // complete or override with WEB-INF properties files
143         loadPropertiesFiles(propertiesFilesString, rootPath);
144 
145         // complete or override with JVM system properties
146         overloadWithSystemProperties();
147 
148         // resolve nested properties
149         resolveNestedProperties();
150     }
151 
152     private void resolveNestedProperties() {
153 
154         Properties sysProps = SystemProperty.getProperties();
155 
156         for (Iterator<Object> it = sysProps.keySet().iterator(); it.hasNext();) {
157             String key = (String) it.next();
158             String oldValue = (String) sysProps.get(key);
159             String value = parseStringValue(oldValue, new HashSet<String>());
160             SystemProperty.getProperties().put(key, value.trim());
161         }
162 
163     }
164 
165     public void loadAllModuleProperties() {
166         // complete or override with modules' properties
167         final List<ModuleDefinition> moduleDefinitions = moduleRegistry.getModuleDefinitions();
168         loadModuleProperties(moduleDefinitions);
169     }
170 
171     /**
172      * Load the properties defined in the module descriptors. They can get overridden later in the properties files in
173      * WEB-INF
174      */
175     protected void loadModuleProperties(List<ModuleDefinition> moduleDefinitions) {
176         for (ModuleDefinition module : moduleDefinitions) {
177             for (PropertyDefinition property : module.getProperties()) {
178                 SystemProperty.setProperty(property.getName(), property.getValue());
179             }
180         }
181     }
182 
183     public void loadPropertiesFiles(String propertiesLocationString, String rootPath) {
184 
185         String[] propertiesLocation = StringUtils.split(propertiesLocationString, ',');
186 
187         boolean found = false;
188         // attempt to load each properties file at the given locations in reverse order: first files in the list
189         // override the later ones
190         for (int j = propertiesLocation.length - 1; j >= 0; j--) {
191             String location = StringUtils.trim(propertiesLocation[j]);
192 
193             if (loadPropertiesFile(rootPath, location)) {
194                 found = true;
195             }
196         }
197 
198         if (!found) {
199             final String msg = MessageFormat.format("No configuration found using location list {0}. Base path is [{1}]", ArrayUtils.toString(propertiesLocation), rootPath); //$NON-NLS-1$
200             log.error(msg);
201             throw new ConfigurationException(msg);
202         }
203     }
204 
205     /**
206      * @deprecated since 4.5, replaced by a new ClasspathPropertySource("/mgnl-beans.properties").
207      * @see info.magnolia.init.DefaultMagnoliaConfigurationProperties
208      */
209     public void loadBeanProperties() {
210         InputStream mgnlbeansStream = getClass().getResourceAsStream(MGNL_BEANS_PROPERTIES);
211 
212         if (mgnlbeansStream != null) {
213             Properties mgnlbeans = new Properties();
214             try {
215                 mgnlbeans.load(mgnlbeansStream);
216             }
217             catch (IOException e) {
218                 log.error("Unable to load {} due to an IOException: {}", MGNL_BEANS_PROPERTIES, e.getMessage());
219             }
220             finally {
221                 IOUtils.closeQuietly(mgnlbeansStream);
222             }
223 
224             for (Iterator<Object> iter = mgnlbeans.keySet().iterator(); iter.hasNext();) {
225                 String key = (String) iter.next();
226                 SystemProperty.setProperty(key, mgnlbeans.getProperty(key));
227             }
228 
229         }
230         else {
231             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);
232         }
233     }
234 
235     /**
236      * Try to load a magnolia.properties file.
237      * @param rootPath
238      * @param location
239      * @return
240      */
241     public boolean loadPropertiesFile(String rootPath, String location) {
242         final File initFile;
243         if (Path.isAbsolute(location)) {
244             initFile = new File(location);
245         }
246         else {
247             initFile = new File(rootPath, location);
248         }
249 
250         if (!initFile.exists() || initFile.isDirectory()) {
251             log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath()); //$NON-NLS-1$
252             return false;
253         }
254 
255         InputStream fileStream = null;
256         try {
257             fileStream = new FileInputStream(initFile);
258         }
259         catch (FileNotFoundException e1) {
260             log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath());
261             return false;
262         }
263 
264         try {
265             SystemProperty.getProperties().load(fileStream);
266             log.info("Loading configuration at {}", initFile.getAbsolutePath());//$NON-NLS-1$
267         }
268         catch (Exception e) {
269             log.error(e.getMessage(), e);
270             return false;
271         }
272         finally {
273             IOUtils.closeQuietly(fileStream);
274         }
275         return true;
276     }
277 
278     /**
279      * Overload the properties with set system properties.
280      */
281     public void overloadWithSystemProperties() {
282         Iterator<Object> it = SystemProperty.getProperties().keySet().iterator();
283         while (it.hasNext()) {
284             String key = (String) it.next();
285             if (System.getProperties().containsKey(key)) {
286                 log.info("system property found: {}", key);
287                 String value = System.getProperty(key);
288                 SystemProperty.setProperty(key, value);
289             }
290         }
291     }
292 
293     /**
294      * Returns the property files configuration string passed, replacing all the needed values: ${servername} will be
295      * reaplced with the name of the server, ${webapp} will be replaced with webapp name, ${systemProperty/*} will be
296      * replaced with the corresponding system property, ${env/*} will be replaced with the corresponding environment property,
297      * and ${contextAttribute/*} and ${contextParam/*} will be replaced with the corresponding attributes or parameters
298      * taken from servlet context.
299      * This can be very useful for all those application servers that has multiple instances on the same server, like
300      * WebSphere. Typical usage in this case:
301      * <code>WEB-INF/config/${servername}/${contextAttribute/com.ibm.websphere.servlet.application.host}/magnolia.properties</code>
302      *
303      * @param context Servlet context
304      * @param servername Server name
305      * @param webapp Webapp name
306      * @param propertiesFilesString a comma separated list of paths.
307      * @return Property file configuration string with everything replaced.
308      *
309      * @deprecated since 4.5, this is done by {@link info.magnolia.init.DefaultMagnoliaPropertiesResolver#DefaultMagnoliaPropertiesResolver}.
310      */
311     public static String processPropertyFilesString(ServletContext context, String servername, String webapp,
312         String propertiesFilesString) {
313         // Replacing basic properties.
314         propertiesFilesString = StringUtils.replace(propertiesFilesString, "${servername}", servername); //$NON-NLS-1$
315         propertiesFilesString = StringUtils.replace(propertiesFilesString, "${webapp}", webapp); //$NON-NLS-1$
316 
317         // Replacing servlet context attributes (${contextAttribute/something})
318         String[] contextAttributeNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX);
319         if (contextAttributeNames != null) {
320             for (String ctxAttrName : contextAttributeNames) {
321                 if (ctxAttrName != null) {
322                     // Some implementation may not accept a null as attribute key, but all should accept an empty
323                     // string.
324                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX + ctxAttrName + PLACEHOLDER_SUFFIX;
325                     final Object attrValue = context.getAttribute(ctxAttrName);
326                     if (attrValue != null) {
327                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, attrValue.toString());
328                     }
329                 }
330             }
331         }
332 
333         // Replacing servlet context parameters (${contextParam/something})
334         String[] contextParamNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_PARAM_PLACEHOLDER_PREFIX);
335         if (contextParamNames != null) {
336             for (String ctxParamName : contextParamNames) {
337                 if (ctxParamName != null) {
338                     // Some implementation may not accept a null as param key, but an empty string? TODO Check.
339                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_PARAM_PLACEHOLDER_PREFIX + ctxParamName + PLACEHOLDER_SUFFIX;
340                     final String paramValue = context.getInitParameter(ctxParamName);
341                     if (paramValue != null) {
342                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue);
343                     }
344                 }
345             }
346         }
347 
348         // Replacing system property (${systemProperty/something})
349         String[] systemPropertiesNames = getNamesBetweenPlaceholders(propertiesFilesString, SYSTEM_PROPERTY_PLACEHOLDER_PREFIX);
350         if (systemPropertiesNames != null) {
351             for (String sysPropName : systemPropertiesNames) {
352                 if (StringUtils.isNotBlank(sysPropName)) {
353                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + SYSTEM_PROPERTY_PLACEHOLDER_PREFIX + sysPropName + PLACEHOLDER_SUFFIX;
354                     final String paramValue = System.getProperty(sysPropName);
355                     if (paramValue != null) {
356                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue);
357                     }
358                 }
359             }
360         }
361 
362         // Replacing environment property (${env/something})
363         String[] envPropertiesNames = getNamesBetweenPlaceholders(propertiesFilesString, ENV_PROPERTY_PLACEHOLDER_PREFIX);
364         if (envPropertiesNames != null) {
365             for (String envPropName : envPropertiesNames) {
366                 if (StringUtils.isNotBlank(envPropName)) {
367                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + ENV_PROPERTY_PLACEHOLDER_PREFIX + envPropName + PLACEHOLDER_SUFFIX;
368                     final String paramValue = System.getenv(envPropName);
369                     if (paramValue != null) {
370                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue);
371                     }
372                 }
373             }
374         }
375 
376         return propertiesFilesString;
377     }
378 
379     private static String[] getNamesBetweenPlaceholders(String propertiesFilesString, String contextNamePlaceHolder) {
380         final String[] names = StringUtils.substringsBetween(
381                 propertiesFilesString,
382                 PLACEHOLDER_PREFIX + contextNamePlaceHolder,
383                 PLACEHOLDER_SUFFIX);
384         return StringUtils.stripAll(names);
385     }
386 
387     /**
388      * Parse the given String value recursively, to be able to resolve nested placeholders. Partly borrowed from
389      * org.springframework.beans.factory.config.PropertyPlaceholderConfigurer (original author: Juergen Hoeller)
390      *
391      * @deprecated since 4.5 this is now done by {@link info.magnolia.init.AbstractMagnoliaConfigurationProperties#parseStringValue}.
392      */
393     protected String parseStringValue(String strVal, Set<String> visitedPlaceholders) {
394 
395         StringBuffer buf = new StringBuffer(strVal);
396 
397         int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
398         while (startIndex != -1) {
399             int endIndex = -1;
400 
401             int index = startIndex + PLACEHOLDER_PREFIX.length();
402             int withinNestedPlaceholder = 0;
403             while (index < buf.length()) {
404                 if (PLACEHOLDER_SUFFIX.equals(buf.subSequence(index, index + PLACEHOLDER_SUFFIX.length()))) {
405                     if (withinNestedPlaceholder > 0) {
406                         withinNestedPlaceholder--;
407                         index = index + PLACEHOLDER_SUFFIX.length();
408                     }
409                     else {
410                         endIndex = index;
411                         break;
412                     }
413                 }
414                 else if (PLACEHOLDER_PREFIX.equals(buf.subSequence(index, index + PLACEHOLDER_PREFIX.length()))) {
415                     withinNestedPlaceholder++;
416                     index = index + PLACEHOLDER_PREFIX.length();
417                 }
418                 else {
419                     index++;
420                 }
421             }
422 
423             if (endIndex != -1) {
424                 String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
425                 if (!visitedPlaceholders.add(placeholder)) {
426 
427                     log.warn("Circular reference detected in properties, \"{}\" is not resolvable", strVal);
428                     return strVal;
429                 }
430                 // Recursive invocation, parsing placeholders contained in the placeholder key.
431                 placeholder = parseStringValue(placeholder, visitedPlaceholders);
432                 // Now obtain the value for the fully resolved key...
433                 String propVal = SystemProperty.getProperty(placeholder);
434                 if (propVal != null) {
435                     // Recursive invocation, parsing placeholders contained in the
436                     // previously resolved placeholder value.
437                     propVal = parseStringValue(propVal, visitedPlaceholders);
438                     buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
439                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
440                 }
441                 else {
442                     // Proceed with unprocessed value.
443                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
444                 }
445                 visitedPlaceholders.remove(placeholder);
446             }
447             else {
448                 startIndex = -1;
449             }
450         }
451 
452         return buf.toString();
453     }
454 
455 }