View Javadoc

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