001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.util.*;
027:
028: import javax.servlet.ServletContext;
029:
030: /**
031: * Property Reader for the WikiEngine. Reads the properties for the WikiEngine
032: * and implements the feature of cascading properties and variable substitution,
033: * which come in handy in a multi wiki installation environment: It reduces the
034: * need for (shell) scripting in order to generate different jspwiki.properties
035: * to a minimum.
036: *
037: * @author Christoph Sauer
038: * @since 2.5.x
039: */
040: public final class PropertyReader {
041:
042: /** The web.xml parameter that defines where the config file is to be found.
043: * If it is not defined, uses the default as defined by DEFAULT_PROPERTYFILE.
044: * {@value #DEFAULT_PROPERTYFILE}
045: */
046: public static final String PARAM_PROPERTYFILE = "jspwiki.propertyfile";
047:
048: public static final String PARAM_PROPERTYFILE_CASCADEPREFIX = "jspwiki.propertyfile.cascade.";
049:
050: /** Path to the default property file.
051: * {@value #DEFAULT_PROPERTYFILE}
052: */
053: public static final String DEFAULT_PROPERTYFILE = "/WEB-INF/jspwiki.properties";
054:
055: public static final String PARAM_VAR_DECLARATION = "var.";
056: public static final String PARAM_VAR_IDENTIFIER = "$";
057:
058: /**
059: * Contains the default properties for JSPWiki.
060: */
061: private static final String[] DEFAULT_PROPERTIES = {
062: "jspwiki.specialPage.Login", "Login.jsp",
063: "jspwiki.specialPage.Logout", "Logout.jsp",
064: "jspwiki.specialPage.CreateGroup", "NewGroup.jsp",
065: "jspwiki.specialPage.CreateProfile", "Register.jsp",
066: "jspwiki.specialPage.EditProfile", "UserPreferences.jsp",
067: "jspwiki.specialPage.Preferences", "UserPreferences.jsp",
068: "jspwiki.specialPage.Search", "Search.jsp",
069: "jspwiki.specialPage.FindPage", "FindPage.jsp" };
070:
071: /**
072: * Private constructor to prevent instantiation.
073: */
074: private PropertyReader() {
075: }
076:
077: /**
078: * Loads the webapp properties based on servlet context information.
079: * Returns a Properties object containing the settings, or null if unable
080: * to load it. (The default file is WEB-INF/jspwiki.properties, and can
081: * be overridden by setting PARAM_PROPERTYFILE in the server or webapp
082: * configuration.)
083: *
084: * <h3>Cascading Properties</h3>
085: * <p>
086: * You can define additional property files and merge them into the default
087: * properties file in a similar process to how you define cascading style
088: * sheets; hence we call this <i>cascading property files</i>. This way you
089: * can overwrite the default values and only specify the properties you
090: * need to change in a multiple wiki environment.
091: * <p>
092: * You define a cascade in the context mapping of your servlet container.
093: * <pre>
094: * jspwiki.properties.cascade.1
095: * jspwiki.properties.cascade.2
096: * jspwiki.properties.cascade.3
097: * </pre>
098: * and so on. You have to number your cascade in a descending way starting
099: * with "1". This means you cannot leave out numbers in your cascade. This
100: * method is based on an idea by Olaf Kaus, see [JSPWiki:MultipleWikis].
101: */
102: public static Properties loadWebAppProps(ServletContext context) {
103: String propertyFile = context
104: .getInitParameter(PARAM_PROPERTYFILE);
105: InputStream propertyStream = null;
106:
107: try {
108: //
109: // Figure out where our properties lie.
110: //
111: if (propertyFile == null) {
112: context
113: .log("No "
114: + PARAM_PROPERTYFILE
115: + " defined for this context, using default from "
116: + DEFAULT_PROPERTYFILE);
117: // Use the default property file.
118: propertyStream = context
119: .getResourceAsStream(DEFAULT_PROPERTYFILE);
120: } else {
121: context.log("Reading properties from " + propertyFile
122: + " instead of default.");
123: propertyStream = new FileInputStream(new File(
124: propertyFile));
125: }
126:
127: if (propertyStream == null) {
128: throw new WikiException(
129: "Property file cannot be found!" + propertyFile);
130: }
131:
132: Properties props = getDefaultProperties();
133: props.load(propertyStream);
134:
135: //this will add additional properties to the default ones:
136: context.log("Loading cascading properties...");
137:
138: //now load the cascade (new in 2.5)
139: loadWebAppPropsCascade(context, props);
140:
141: //finally expand the variables (new in 2.5)
142: expandVars(props);
143:
144: return props;
145: } catch (Exception e) {
146: context
147: .log(Release.APPNAME
148: + ": Unable to load and setup properties from jspwiki.properties. "
149: + e.getMessage());
150: } finally {
151: try {
152: if (propertyStream != null)
153: propertyStream.close();
154: } catch (IOException e) {
155: context
156: .log("Unable to close property stream - something must be seriously wrong.");
157: }
158: }
159:
160: return null;
161: }
162:
163: /**
164: * Returns the default property set as a Properties object.
165: */
166: public static final Properties getDefaultProperties() {
167: return new Properties(TextUtil
168: .createProperties(DEFAULT_PROPERTIES));
169: }
170:
171: /**
172: * Returns the ServletContext Init parameter if has been set, otherwise
173: * checks for a System property of the same name. If neither are defined,
174: * returns null. This permits both Servlet- and System-defined cascading
175: * properties.
176: */
177: private static String getInitParameter(ServletContext context,
178: String name) {
179: String value = context.getInitParameter(name);
180: return (value != null) ? value : System.getProperty(name);
181: }
182:
183: /**
184: * Implement the cascade functionality.
185: *
186: * @param context where to read the cascade from
187: * @param defaultProperties properties to merge the cascading properties to
188: * @author Christoph Sauer
189: * @since 2.5.x
190: */
191: private static void loadWebAppPropsCascade(ServletContext context,
192: Properties defaultProperties) {
193: if (getInitParameter(context, PARAM_PROPERTYFILE_CASCADEPREFIX
194: + "1") == null) {
195: context
196: .log(" No cascading properties defined for this context");
197: return;
198: }
199:
200: // get into cascade...
201: int depth = 0;
202: boolean more = true;
203: InputStream propertyStream = null;
204: while (more) {
205: depth++;
206: String propertyFile = getInitParameter(context,
207: PARAM_PROPERTYFILE_CASCADEPREFIX + depth);
208:
209: if (propertyFile == null) {
210: more = false;
211: break;
212: }
213:
214: try {
215: context.log(" Reading additional properties from "
216: + propertyFile + " and merge to cascade.");
217: Properties additionalProps = new Properties();
218: propertyStream = new FileInputStream(new File(
219: propertyFile));
220: additionalProps.load(propertyStream);
221: defaultProperties.putAll(additionalProps);
222: } catch (Exception e) {
223: context.log(" " + Release.APPNAME
224: + ": Unable to load and setup properties from "
225: + propertyFile + "." + e.getMessage());
226: } finally {
227: try {
228: if (propertyStream != null) {
229: propertyStream.close();
230: }
231: } catch (IOException e) {
232: context
233: .log(" Unable to close property stream - something must be seriously wrong.");
234: }
235: }
236: }
237:
238: return;
239: }
240:
241: /**
242: * You define a property variable by using the prefix "var.x" as a
243: * property. In property values you can then use the "$x" identifier
244: * to use this variable.
245: *
246: * For example you could declare a base directory for all your files
247: * like this and use it in all your other property definitions with
248: * a "$basedir". Note that it does not matter if you define the
249: * variable before its usage.
250: * <pre>
251: * var.basedir = /p/mywiki;
252: * jspwiki.fileSystemProvider.pageDir = $basedir/www/
253: * jspwiki.basicAttachmentProvider.storageDir = $basedir/www/
254: * jspwiki.workDir = $basedir/wrk/
255: * </pre>
256: *
257: * @param properties - properties to expand;
258: */
259: public static void expandVars(Properties properties) {
260: //get variable name/values from properties...
261: Map vars = new HashMap();
262: Enumeration propertyList = properties.propertyNames();
263: while (propertyList.hasMoreElements()) {
264: String propertyName = (String) propertyList.nextElement();
265: String propertyValue = properties.getProperty(propertyName);
266:
267: if (propertyName.startsWith(PARAM_VAR_DECLARATION)) {
268: String varName = propertyName.substring(4,
269: propertyName.length()).trim();
270: String varValue = propertyValue.trim();
271: vars.put(varName, varValue);
272: }
273: }
274:
275: //now, substitute $ values in property values with vars...
276: propertyList = properties.propertyNames();
277: while (propertyList.hasMoreElements()) {
278: String propertyName = (String) propertyList.nextElement();
279: String propertyValue = properties.getProperty(propertyName);
280:
281: //skip var properties itself...
282: if (propertyName.startsWith(PARAM_VAR_DECLARATION)) {
283: continue;
284: }
285:
286: Iterator iter = vars.entrySet().iterator();
287: while (iter.hasNext()) {
288: Map.Entry entry = (Map.Entry) iter.next();
289: String varName = (String) entry.getKey();
290: String varValue = (String) entry.getValue();
291:
292: //replace old property value, using the same variabe. If we don't overwrite
293: //the same one the next loop works with the original one again and
294: //multiple var expansion won't work...
295: propertyValue = TextUtil.replaceString(propertyValue,
296: PARAM_VAR_IDENTIFIER + varName, varValue);
297:
298: //add the new PropertyValue to the properties
299: properties.put(propertyName, propertyValue);
300:
301: }
302: }
303: }
304:
305: }
|