View Javadoc

1   /**
2    * This file Copyright (c) 2008-2010 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.ModuleManagementException;
39  import info.magnolia.module.ModuleManager;
40  import info.magnolia.module.model.ModuleDefinition;
41  import info.magnolia.module.model.PropertyDefinition;
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 info.magnolia.objectfactory.Components;
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  /**
66   * This class is responsible for loading the various "magnolia.properties" files, merging them,
67   * and substituting variables in their values.
68   * @author pbracher
69   * @author fgiust
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     public static PropertiesInitializer getInstance() {
102         return Components.getSingleton(PropertiesInitializer.class);
103     }
104 
105     /**
106      * Default value for the MAGNOLIA_INITIALIZATION_FILE parameter.
107      */
108     public static final String DEFAULT_INITIALIZATION_PARAMETER = //
109     "WEB-INF/config/${servername}/${webapp}/magnolia.properties," //$NON-NLS-1$
110         + "WEB-INF/config/${servername}/magnolia.properties," //$NON-NLS-1$
111         + "WEB-INF/config/${webapp}/magnolia.properties," //$NON-NLS-1$
112         + "WEB-INF/config/default/magnolia.properties," //$NON-NLS-1$
113         + "WEB-INF/config/magnolia.properties"; //$NON-NLS-1$
114 
115     public void loadAllProperties(String propertiesFilesString, String rootPath) {
116         // load mgnl-beans.properties first
117         loadBeanProperties();
118 
119         loadAllModuleProperties();
120 
121         // complete or override with WEB-INF properties files
122         loadPropertiesFiles(propertiesFilesString, rootPath);
123 
124         // complete or override with JVM system properties
125         overloadWithSystemProperties();
126 
127         // resolve nested properties
128         resolveNestedProperties();
129     }
130 
131     private void resolveNestedProperties() {
132 
133         Properties sysProps = SystemProperty.getProperties();
134 
135         for (Iterator it = sysProps.keySet().iterator(); it.hasNext();) {
136             String key = (String) it.next();
137             String oldValue = (String) sysProps.get(key);
138             String value = parseStringValue(oldValue, new HashSet<String>());
139             SystemProperty.getProperties().put(key, value);
140         }
141 
142     }
143 
144     public void loadAllModuleProperties() {
145         // complete or override with modules' properties
146         final ModuleManager moduleManager = ModuleManager.Factory.getInstance();
147         try {
148             final List<ModuleDefinition> moduleDefinitions = moduleManager.loadDefinitions();
149             loadModuleProperties(moduleDefinitions);
150         }
151         catch (ModuleManagementException e) {
152             log.error(e.getMessage());
153             throw new RuntimeException(e); // TODO
154         }
155     }
156 
157     /**
158      * Load the properties defined in the module descriptors. They can get overridden later in the properties files in
159      * WEB-INF
160      */
161     protected void loadModuleProperties(List<ModuleDefinition> moduleDefinitions) {
162         for (ModuleDefinition module : moduleDefinitions) {
163             for (PropertyDefinition property : module.getProperties()) {
164                 SystemProperty.setProperty(property.getName(), property.getValue());
165             }
166         }
167     }
168 
169     public void loadPropertiesFiles(String propertiesLocationString, String rootPath) {
170 
171         String[] propertiesLocation = StringUtils.split(propertiesLocationString, ',');
172 
173         boolean found = false;
174         // attempt to load each properties file at the given locations in reverse order: first files in the list
175         // override the later ones
176         for (int j = propertiesLocation.length - 1; j >= 0; j--) {
177             String location = StringUtils.trim(propertiesLocation[j]);
178 
179             if (loadPropertiesFile(rootPath, location)) {
180                 found = true;
181             }
182         }
183 
184         if (!found) {
185             final String msg = MessageFormat.format("No configuration found using location list {0}. Base path is [{1}]", ArrayUtils.toString(propertiesLocation), rootPath); //$NON-NLS-1$
186             log.error(msg);
187             throw new ConfigurationException(msg);
188         }
189     }
190 
191     public void loadBeanProperties() {
192         InputStream mgnlbeansStream = getClass().getResourceAsStream(MGNL_BEANS_PROPERTIES);
193 
194         if (mgnlbeansStream != null) {
195             Properties mgnlbeans = new Properties();
196             try {
197                 mgnlbeans.load(mgnlbeansStream);
198             }
199             catch (IOException e) {
200                 log.error("Unable to load {} due to an IOException: {}", MGNL_BEANS_PROPERTIES, e.getMessage());
201             }
202             finally {
203                 IOUtils.closeQuietly(mgnlbeansStream);
204             }
205 
206             for (Iterator iter = mgnlbeans.keySet().iterator(); iter.hasNext();) {
207                 String key = (String) iter.next();
208                 SystemProperty.setProperty(key, mgnlbeans.getProperty(key));
209             }
210 
211         }
212         else {
213             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);
214         }
215     }
216 
217     /**
218      * Try to load a magnolia.properties file.
219      * @param rootPath
220      * @param location
221      * @return
222      */
223     public boolean loadPropertiesFile(String rootPath, String location) {
224         final File initFile;
225         if (Path.isAbsolute(location)) {
226             initFile = new File(location);
227         }
228         else {
229             initFile = new File(rootPath, location);
230         }
231 
232         if (!initFile.exists() || initFile.isDirectory()) {
233             log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath()); //$NON-NLS-1$
234             return false;
235         }
236 
237         InputStream fileStream = null;
238         try {
239             fileStream = new FileInputStream(initFile);
240         }
241         catch (FileNotFoundException e1) {
242             log.debug("Configuration file not found with path [{}]", initFile.getAbsolutePath());
243             return false;
244         }
245 
246         try {
247             SystemProperty.getProperties().load(fileStream);
248             log.info("Loading configuration at {}", initFile.getAbsolutePath());//$NON-NLS-1$
249         }
250         catch (Exception e) {
251             log.error(e.getMessage(), e);
252             return false;
253         }
254         finally {
255             IOUtils.closeQuietly(fileStream);
256         }
257         return true;
258     }
259 
260     /**
261      * Overload the properties with set system properties.
262      */
263     public void overloadWithSystemProperties() {
264         Iterator it = SystemProperty.getProperties().keySet().iterator();
265         while (it.hasNext()) {
266             String key = (String) it.next();
267             if (System.getProperties().containsKey(key)) {
268                 log.info("system property found: {}", key);
269                 String value = System.getProperty(key);
270                 SystemProperty.setProperty(key, value);
271             }
272         }
273     }
274 
275     /**
276      * Returns the property files configuration string passed, replacing all the needed values: ${servername} will be
277      * reaplced with the name of the server, ${webapp} will be replaced with webapp name, and ${contextAttribute/*} and
278      * ${contextParam/*} will be replaced with the corresponding attributes or parameters taken from servlet context.
279      * This can be very useful for all those application servers that has multiple instances on the same server, like
280      * WebSphere. Typical usage in this case:
281      * <code>WEB-INF/config/${servername}/${contextAttribute/com.ibm.websphere.servlet.application.host}/magnolia.properties</code>
282      * @param context Servlet context
283      * @param servername Server name
284      * @param webapp Webapp name
285      * @param propertiesFilesString Property file configuration string.
286      * @return Property file configuration string with everything replaced.
287      */
288     public static String processPropertyFilesString(ServletContext context, String servername, String webapp,
289         String propertiesFilesString) {
290         // Replacing basic properties.
291         propertiesFilesString = StringUtils.replace(propertiesFilesString, "${servername}", servername); //$NON-NLS-1$
292         propertiesFilesString = StringUtils.replace(propertiesFilesString, "${webapp}", webapp); //$NON-NLS-1$
293 
294         // Replacing servlet context attributes (${contextAttribute/something})
295         String[] contextAttributeNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX);
296         if (contextAttributeNames != null) {
297             for (String ctxAttrName : contextAttributeNames) {
298                 if (ctxAttrName != null) {
299                     // Some implementation may not accept a null as attribute key, but all should accept an empty
300                     // string.
301                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX + ctxAttrName + PLACEHOLDER_SUFFIX;
302                     final Object attrValue = context.getAttribute(ctxAttrName);
303                     if (attrValue != null) {
304                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, attrValue.toString());
305                     }
306                 }
307             }
308         }
309 
310         // Replacing servlet context parameters (${contextParam/something})
311         String[] contextParamNames = getNamesBetweenPlaceholders(propertiesFilesString, CONTEXT_PARAM_PLACEHOLDER_PREFIX);
312         if (contextParamNames != null) {
313             for (String ctxParamName : contextParamNames) {
314                 if (ctxParamName != null) {
315                     // Some implementation may not accept a null as param key, but an empty string? TODO Check.
316                     final String originalPlaceHolder = PLACEHOLDER_PREFIX + CONTEXT_PARAM_PLACEHOLDER_PREFIX + ctxParamName + PLACEHOLDER_SUFFIX;
317                     final String paramValue = context.getInitParameter(ctxParamName);
318                     if (paramValue != null) {
319                         propertiesFilesString = propertiesFilesString.replace(originalPlaceHolder, paramValue);
320                     }
321                 }
322             }
323         }
324 
325         return propertiesFilesString;
326     }
327 
328     private static String[] getNamesBetweenPlaceholders(String propertiesFilesString, String contextNamePlaceHolder) {
329         final String[] names = StringUtils.substringsBetween(
330                 propertiesFilesString,
331                 PLACEHOLDER_PREFIX + contextNamePlaceHolder,
332                 PLACEHOLDER_SUFFIX);
333         return StringUtils.stripAll(names);
334     }
335 
336     /**
337      * Parse the given String value recursively, to be able to resolve nested placeholders. Partly borrowed from
338      * org.springframework.beans.factory.config.PropertyPlaceholderConfigurer (original author: Juergen Hoeller)
339      */
340     protected String parseStringValue(String strVal, Set<String> visitedPlaceholders) {
341 
342         StringBuffer buf = new StringBuffer(strVal);
343 
344         int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
345         while (startIndex != -1) {
346             int endIndex = -1;
347 
348             int index = startIndex + PLACEHOLDER_PREFIX.length();
349             int withinNestedPlaceholder = 0;
350             while (index < buf.length()) {
351                 if (PLACEHOLDER_SUFFIX.equals(buf.subSequence(index, index + PLACEHOLDER_SUFFIX.length()))) {
352                     if (withinNestedPlaceholder > 0) {
353                         withinNestedPlaceholder--;
354                         index = index + PLACEHOLDER_SUFFIX.length();
355                     }
356                     else {
357                         endIndex = index;
358                         break;
359                     }
360                 }
361                 else if (PLACEHOLDER_PREFIX.equals(buf.subSequence(index, index + PLACEHOLDER_PREFIX.length()))) {
362                     withinNestedPlaceholder++;
363                     index = index + PLACEHOLDER_PREFIX.length();
364                 }
365                 else {
366                     index++;
367                 }
368             }
369 
370             if (endIndex != -1) {
371                 String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
372                 if (!visitedPlaceholders.add(placeholder)) {
373 
374                     log.warn("Circular reference detected in properties, \"{}\" is not resolvable", strVal);
375                     return strVal;
376                 }
377                 // Recursive invocation, parsing placeholders contained in the placeholder key.
378                 placeholder = parseStringValue(placeholder, visitedPlaceholders);
379                 // Now obtain the value for the fully resolved key...
380                 String propVal = SystemProperty.getProperty(placeholder);
381                 if (propVal != null) {
382                     // Recursive invocation, parsing placeholders contained in the
383                     // previously resolved placeholder value.
384                     propVal = parseStringValue(propVal, visitedPlaceholders);
385                     buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
386                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
387                 }
388                 else {
389                     // Proceed with unprocessed value.
390                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
391                 }
392                 visitedPlaceholders.remove(placeholder);
393             }
394             else {
395                 startIndex = -1;
396             }
397         }
398 
399         return buf.toString();
400     }
401 
402 }