001: /*
002: * ConfigFileHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1999-2000 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, rinaldo, suhler.
022: *
023: * Version: 1.31
024: * Created by suhler on 99/06/28
025: * Last modified by suhler on 00/12/11 13:28:21
026: */
027:
028: package sunlabs.brazil.handler;
029:
030: import sunlabs.brazil.session.SessionManager;
031: import sunlabs.brazil.server.Handler;
032: import sunlabs.brazil.server.Request;
033: import sunlabs.brazil.server.Server;
034: import sunlabs.brazil.util.Glob;
035: import sunlabs.brazil.server.Request.RechainableProperties;
036:
037: import java.io.File;
038: import java.io.FileInputStream;
039: import java.io.FileOutputStream;
040: import java.io.IOException;
041: import java.util.Enumeration;
042: import java.util.Dictionary;
043: import java.util.Hashtable;
044: import java.util.Properties;
045:
046: /**
047: * Handler for manipulating per-user session state that can be
048: * represented as ASCII name/value pairs. The state for each session
049: * is stored in a file, which is expected to be in java properties format.
050: *
051: * If "prefix" is matched, the contents of the (usually cached) config file
052: * for the current session is added to the request properties.
053: *
054: * If the url matches the "set" property, the contents of the config
055: * file are changed based on the supplied query parameters (either GET
056: * of POST). If no config file exists for the session, one is created
057: * from a default properties file. Only properties already in the
058: * config file may be changed using the "set" method.
059: *
060: * If a "%" is specified in the file name, it is replaced by the
061: * SessionID property, if any, or "common" if sessions aren't used.
062: * This should be replaced with something more general, so we can have
063: * more arbitrary mappings between request and the session info.
064: * <p>
065: * The following request properties are used:
066: * <dl class=props>
067: * <dt>prefix <dd> The URL prefix required for all documents
068: * <dt>set <dd> The url pattern to match setting properties.
069: * Currently, it must also match "prefix".
070: * <dt>noContent<dd> a url, matching the "set" pattern that causes
071: * a "204 no content" to be returned to the client
072: * (experimental).
073: * <dt>name <dd> The name of the config file. the first "%" is replaced
074: * by the current SessionID.
075: * <dt>default <dd> The default properties file to "seed" session properties
076: * <dt>glob <dd> Properties that match this "glob" pattern may be set
077: * using the "set" pattern. If this property is specified,
078: * the "default" property is optional.
079: * <dt>root <dd> The document root (no properties prefix required). If the
080: * "name" or "default" properties don't start with a "/",
081: * this is used as the current directory.
082: *
083: * </dl>
084: * If "%" is specified in the file name, a new session file is
085: * created only if 1) a property is changed from the default, and 2)
086: * A cookie was received by the browser.
087: * <p>
088: * See also: {@link sunlabs.brazil.template.SetTemplate} and
089: * {@link sunlabs.brazil.session.PropertiesCacheManager} which may
090: * be used (together) to provide a templated based (instead of URL based)
091: * mechanism for maintaining persistent properties.
092: * <p>
093: * Note1: This version uses ReChainableProperties.
094: *
095: * @author Stephen Uhler
096: * @version 1.31, 00/12/11
097: */
098:
099: public class ConfigFileHandler implements Handler {
100: static final String SET = "set"; // glob matching url setting props
101: static final String PREFIX = "prefix"; // prefix for adding to props
102: static final String NAME = "name"; // name of the config file
103: static final String DEFAULT = "default"; // the default config file
104: static final String ROOT = "root"; // root of config file
105: static final String GLOB = "glob";
106:
107: String propsPrefix;
108: String name; // name of the config file
109: String urlPrefix; // prefix for any request we handle
110: String set; // glob pattern to match url needed to modify props
111: String nc; // glob pattern to match url needed for no content
112: String match; // glob pattern to match settable props not in deflt file
113: String root; // where to find the root of the config file
114: Properties defaultProperties; // where to find the defaults
115: boolean copy = false; // undocumented backwart compat flag
116:
117: /**
118: * Make sure default properties exist before starting this handler,
119: * or that "match" is specified".
120: */
121:
122: public boolean init(Server server, String prefix) {
123: propsPrefix = prefix;
124: urlPrefix = server.props.getProperty(prefix + PREFIX, "/");
125: set = server.props
126: .getProperty(prefix + SET, urlPrefix + prefix);
127: nc = server.props.getProperty(prefix + "noContent", "X");
128: match = server.props.getProperty(prefix + GLOB);
129: name = server.props.getProperty(prefix + NAME, prefix + ".cfg");
130: copy = (server.props.getProperty(prefix + "copy") != null);
131: root = server.props.getProperty(ROOT, ".");
132: String defaultName = server.props.getProperty(prefix + DEFAULT);
133: File defaultFile = null;
134: if (defaultName != null) {
135: if (defaultName.startsWith("/")) {
136: defaultFile = new File(defaultName);
137: } else {
138: defaultFile = new File(root, defaultName);
139: }
140: }
141: defaultProperties = new Properties();
142: try {
143: FileInputStream in = new FileInputStream(defaultFile);
144: defaultProperties.load(in);
145: in.close();
146: } catch (IOException e) {
147: if (match == null) {
148: server.log(Server.LOG_WARNING, prefix,
149: "No default file: " + defaultFile
150: + " ConfigFileHandler NOT installed!");
151: return false;
152: }
153: }
154: server.log(Server.LOG_DIAGNOSTIC, prefix, "\n set=" + set
155: + "\n name=" + name + "\n default=" + defaultFile);
156: return true;
157: }
158:
159: /**
160: * Extract the session state into the request object, optionally
161: * modifying the properties. If the properties are modified,
162: * they are stored in a file for safe keeping.
163: */
164:
165: public boolean
166: respond(Request request) throws IOException {
167: if (!request.url.startsWith(urlPrefix)) {
168: return false;
169: }
170:
171: /*
172: * Get the existing properties.
173: */
174:
175: root = request.props.getProperty(ROOT, root);
176: String id = request.props.getProperty("SessionID", "common");
177: RechainableProperties p = (RechainableProperties)
178: SessionManager.getSession(id, propsPrefix,
179: RechainableProperties.class);
180:
181: /*
182: * If no properties exist, read in the session
183: * properties, otherwise use the default properties
184: */
185:
186: if (p.isEmpty()) {
187: File file = getPropsFile(name, id);
188: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
189: "No properties, looking in: " + file);
190: try {
191: FileInputStream in = new FileInputStream(file);
192: p.load(in);
193: in.close();
194: } catch (IOException e) {
195: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
196: "Can't find config file: " +
197: file + " using default properties");
198: Enumeration enum = defaultProperties.propertyNames();
199: while(enum.hasMoreElements()) {
200: String key = (String) enum.nextElement();
201: p.put(key, defaultProperties.getProperty(key));
202: }
203: }
204: }
205:
206: /*
207: * If "SET" then update the properties file
208: */
209:
210: if (Glob.match(set, request.url)) {
211: Dictionary update = request.getQueryData(null);
212: // System.out.println("Setting from: " + update);
213: Enumeration enum = update.keys();
214: boolean changed = false;
215: synchronized (p) {
216: while(enum.hasMoreElements()) {
217: String key = (String) enum.nextElement();
218: if (p.containsKey(key) || isMatch(key)) {
219: p.put(key, update.get(key));
220: changed = true; // XXX not quite
221: } else {
222: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
223: "Can't set key " + key +
224: ", name doesn't exist in default file" +
225: " or doesn;t match: " + match);
226: }
227: }
228: if (!changed) {
229: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
230: "Set called, nothing changed!!");
231: }
232:
233: /*
234: * If the client has cookies enabled, try to save the
235: * properties. This depends upon a side effect of the
236: * "sessionHandler" to let us know if the browser
237: * actually sent us our cookie.
238: */
239:
240: if (changed && (name.indexOf("%") < 0 ||
241: request.props.containsKey("gotCookie"))) {
242: File file = getPropsFile(name, id);
243: try {
244: FileOutputStream out = new FileOutputStream(file);
245: p.save(out, request.serverUrl() + request.url +
246: " (from ConfigFileHandler: " + propsPrefix + ")");
247: out.close();
248: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
249: "Saving configuration properties to " + file);
250: } catch (IOException e) {
251: request.log(Server.LOG_WARNING, propsPrefix,
252: "Can't save properties to: " + file);
253: }
254: }
255: }
256:
257: /*
258: * If this is a post, trash the input and change to a get.
259: * XXX This is probably incorrect.
260: */
261:
262: if (request.method.equals("POST")) {
263: request.method = "GET";
264: request.postData = null;
265: request.headers.put("Content-Length", "0");
266: }
267: }
268:
269: if (copy) {
270: Enumeration enum = p.keys();
271: while(enum.hasMoreElements()) {
272: String key = (String) enum.nextElement();
273: request.props.put(key, p.getProperty(key));
274: }
275: } else {
276: p.setDefaults(request.props.getDefaults());
277: request.props.setDefaults(p);
278: request.log(Server.LOG_DIAGNOSTIC, propsPrefix, "Chaining " +
279: p + " onto " + request.props);
280: }
281: return false;
282: }
283:
284: /**
285: * True if key "s" matches glob pattern in "match". If
286: * match is null, then no match.
287: */
288:
289: private boolean isMatch(String s) {
290: return (match != null && Glob.match(match, s));
291: }
292:
293: /**
294: * Find the config file name
295: */
296:
297: private File getPropsFile(String name, String id) {
298: int index;
299: if ((index = name.indexOf("%")) >= 0) {
300: name = name.substring(0, index) + id
301: + name.substring(index + 1);
302: }
303: File file;
304: if (name.startsWith("/")) {
305: file = new File(name);
306: } else {
307: file = new File(root, name);
308: }
309: return file;
310: }
311: }
|