001: /*
002: * DynamicConfigHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 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: cstevens.
018: * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.5
024: * Created by cstevens on 00/03/02
025: * Last modified by suhler on 00/12/11 13:28:41
026: */
027:
028: package sunlabs.brazil.handler;
029:
030: import sunlabs.brazil.server.ChainHandler;
031: import sunlabs.brazil.server.Handler;
032: import sunlabs.brazil.server.Request;
033: import sunlabs.brazil.server.Server;
034:
035: import java.io.IOException;
036: import java.util.Hashtable;
037: import java.util.Properties;
038:
039: /**
040: * The <code>DynamicConfigHandler</code> allows the user to change the
041: * configuration of the server and its handlers on the fly. This handler
042: * can therefore be used to swap in and out functionality, for example,
043: * by dynamically adding a new <code>AuthHandler</code> to add a new
044: * form of authentication to a running server.
045: * <p>
046: * This handler uses a special set of URLs to allow a new set of
047: * configuration properties to be uploaded. The new configuration
048: * replaces the old configuration.
049: * <p>
050: * The name of another <code>Handler</code> is supplied when this
051: * <code>DynamicConfigHandler</code> is initialized. This <code>Handler</code>
052: * is the helper or sub-handler for the <code>DynamicConfigHandler</code>.
053: * When the <code>DynamicConfigHandler</code> receives a regular HTTP
054: * request (that matches the URL prefix described below), it redirects
055: * that request to the <code>respond</code> method of the sub-handler.
056: * <p>
057: * The uploaded configuration properties are kept in a separate properties
058: * object from the server's properties. The server's properties
059: * are in fact not accessible from the sub-handler; the sub-handler can
060: * only access and/or change the properties owned by the
061: * <code>DynamicConfigHandler</code>.
062: * <p>
063: * This handler uses the following configuration properties: <dl class=props>
064: *
065: * <dt> handler
066: * <dd> The name of the initial sub-handler that this
067: * <code>DynamicConfigHandler</code> will use to process requests. When
068: * new properties are uploaded, the sub-handler will be replaced with
069: * whatever is specified in the newly uploaded <code>handler</code>
070: * property.
071: *
072: * <dt> prefix
073: * <dd> Only URLs beginning with this string will be redirected to the
074: * sub-handler. This property belongs to the
075: * <code>DynamicConfigHandler</code> and is <b>not</b> changed when
076: * new properties are uploaded. The default is "/".
077: *
078: * <dt> config
079: * <dd> URLs beginning with this prefix can be used to upload or download
080: * new configuration properties to this handler, which will also
081: * dynamically change which sub-handler is installed. This property
082: * belongs to the <code>DynamicConfigHandler</code> and is <b>not</b>
083: * changed when new properties are uploaded. The default
084: * is "/config/".
085: * <p>
086: * Properties may be uploaded by sending them as "name=value" pairs in
087: * the body of a POST or in the "?" query parameters. The URL for
088: * uploading properties is "<i>config</i>/set".
089: * <p>
090: * The current set of properties may be retrieved from this handler by
091: * sending the URL "<i>config</i>/get"
092: * </dl>
093: *
094: * A sample set of configuration parameters illustrating how to use this
095: * handler follows:
096: * <pre>
097: * handler=sunlabs.brazil.server.ChainHandler
098: * port=8081
099: * log=5
100: *
101: * handlers=dyn cgi
102: *
103: * dyn.class=sunlabs.brazil.server.DynamicConfigHandler
104: * dyn.prefix=/sparky/
105: * dyn.config=/config-sparky/
106: * dyn.handler=chain
107: *
108: * chain.class=sunlabs.brazil.server.ChainHandler
109: * chain.handlers=foo baz garply
110: *
111: * foo.class=sunlabs.brazil.handler.HomeDirHandler
112: * foo.home=/home/users/
113: *
114: * baz.class=sunlabs.brazil.handler.FileHandler
115: *
116: * garply.class=sunlabs.brazil.handler.NotFoundHandler
117: * garply.root="/errors/"
118: * garply.fileName="nofile.html"
119: *
120: * cgi.class = sunlabs.brazil.handler.CgiHandler
121: * .
122: * .
123: * .
124: * </pre>
125: * These parameters set up a normal <code>Server</code> on port 8081,
126: * running a <code>ChainHandler</code> which dispatches to a
127: * <code>DynamicConfigHandler</code> and a <code>CgiHandler</code>.
128: * <p>
129: * The <code>DynamicConfigHandler</code> will listen for HTTP requests
130: * beginning with "/sparky/" and dispatch to its dynamically generated
131: * list of handlers, and listen for requests beginning with "/config-sparky/"
132: * to dynamically change that set of handlers.
133: * <p>
134: * To give this <code>DynamicConfigHandler</code> something to do, an initial
135: * set of handlers is provided with the same prefix ("dyn") as the
136: * <code>DynamicConfigHandler</code> itself. The prefix is stripped off
137: * those properties and the revised set of properties is passed to the
138: * <code>DynamicConfigHandler</code> to initialize its dynamically
139: * configurable world.
140: *
141: *
142: * @author Colin Stevens (colin.stevens@sun.com)
143: * @version 1.5, 00/12/11
144: */
145: public class DynamicConfigHandler implements Handler {
146: private static final String URL_PREFIX = "prefix";
147: private static final String CONFIG_PREFIX = "config";
148: private static final String HANDLER = "handler";
149:
150: private static final String CONFIG_SET = "/set";
151: private static final String CONFIG_GET = "/get";
152:
153: String urlPrefix = "/";
154: String configPrefix = "/config";
155:
156: Server server;
157: String prefix;
158: Properties props;
159:
160: String name;
161: Handler handler;
162:
163: /**
164: * Initializes this <code>DynamicConfigHandler</code> by loading the
165: * initial handler. An initial handler does not need to be defined,
166: * however, since the handler configuration can be downloaded later.
167: *
168: * @param server
169: * The HTTP server that created this handler.
170: *
171: * @param prefix
172: * A prefix to prepend to all of the keys that this
173: * handler uses to extract configuration information.
174: *
175: * @return <code>false</code> if the initial handler was specified but
176: * could not be initialized, <code>true</code> otherwise.
177: */
178: public boolean init(Server server, String prefix) {
179: this .server = server;
180: this .prefix = prefix;
181: this .props = server.props;
182:
183: urlPrefix = props.getProperty(prefix + URL_PREFIX, urlPrefix);
184: configPrefix = props.getProperty(prefix + CONFIG_PREFIX,
185: configPrefix);
186:
187: name = props.getProperty(prefix + HANDLER);
188: if (name == null) {
189: return true;
190: }
191: handler = ChainHandler.initHandler(server, prefix + HANDLER
192: + ".", name);
193:
194: return (handler != null);
195: }
196:
197: /**
198: * Responds to an HTTP request by examining the "Host:" request header
199: * and dispatching to the main handler of the server that handles
200: * that virtual host. If the "Host:" request header was not specified,
201: * or named a virtual host that was not initialized in <code>init</code>
202: * from the list of virtual hosts, this method returns without
203: * handling the request.
204: *
205: * @param request
206: * The HTTP request to be forwarded to one of the sub-servers.
207: *
208: * @return <code>true</code> if the sub-server handled the message,
209: * <code>false</code> if it did not. <code>false</code> is
210: * also returned if the "Host:" was unspecified or unknown.
211: */
212: public boolean respond(Request request) throws IOException {
213: if (request.url.startsWith(configPrefix)) {
214: if (request.url.endsWith(CONFIG_GET)) {
215: request.keepAlive = false;
216: request.sendHeaders(200, "text/plain", -1);
217: props.save(request.out, null);
218: return true;
219: } else if (request.url.endsWith(CONFIG_SET)) {
220: Properties local = new Properties(server.props);
221: request.getQueryData(local);
222:
223: String name = local.getProperty(HANDLER, "");
224: Handler h = ChainHandler.initHandler(server, "", name);
225:
226: String result = "result=properties uploaded";
227: if (h == null) {
228: result = "error=properties malformed";
229: } else {
230: handler = h;
231: props = local;
232: }
233: request.sendResponse(result, "text/plain");
234: return true;
235: }
236: } else if (request.url.startsWith(urlPrefix)) {
237: request.log(Server.LOG_DIAGNOSTIC, prefix,
238: "invoking handler: " + name);
239:
240: /*
241: * XXX Defaults for my props are the original server props.
242: * Defaults for request's props are ????
243: *
244: * 1. Should my defaults be null instead?
245: * 2. Should make my defaults be the ???? from the request.
246: * (Threading issue: concurrent requests w/ differing
247: * defaults for their properties).
248: */
249:
250: Properties old = request.props.setDefaults(props);
251: try {
252: return handler.respond(request);
253: } finally {
254: request.props.setDefaults(old);
255: }
256: }
257: return false;
258: }
259: }
|