/* * TemplateHandler.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 1998-2007 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: suhler. * Portions created by suhler are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): cstevens, drach, suhler. * * Version: 2.8 * Created by suhler on 98/09/14 * Last modified by suhler on 07/01/08 15:15:18 * * Version Histories: * * 2.8 07/01/08-15:15:18 (suhler) * set the last-modified property * * 2.7 06/11/13-15:00:12 (suhler) * move MatchString from handler -> util * * 2.6 06/04/25-14:16:39 (suhler) * use consolidated mime type handling in FileHandler * * 2.5 04/12/30-12:38:25 (suhler) * javadoc fixes * * 2.4 04/10/24-17:21:47 (suhler) * bug fix. change "FileName" to "fileName" * * 2.3 03/08/01-16:19:06 (suhler) * fixes for javadoc * * 2.2 03/07/14-09:57:42 (suhler) * added tagPrefix option docs * * 2.1 02/10/01-16:36:47 (suhler) * version change * * 1.42 02/08/21-15:33:51 (suhler) * add "outputEncoding" support * * 1.41 02/06/27-18:38:11 (suhler) * add encoding option * * 1.40 02/06/24-15:40:14 (suhler) * Don't install the template handler is one of the templates fails * * 1.39 02/05/24-16:58:22 (suhler) * add "modified" option to output last-modified times * * 1.38 02/04/24-13:36:11 (suhler) * check for text mime type before processing * * 1.37 02/04/24-12:53:00 (suhler) * check for (and reject) non-text mime types * * 1.36 01/12/07-14:26:28 (suhler) * 'twas quite broken: * - use MatchString instead of old prefix/suffix matching * - fixed redirects and suffix checking, so that this handler may be * - used by itself. * * 1.35 01/08/14-16:39:09 (suhler) * any status code changes were being inadvertantly reset * * 1.34 01/05/02-15:32:06 (drach) * Add template tokens. * * 1.33 01/03/06-09:07:53 (suhler) * make sure specified suffix maps to a valid mime type * * 1.32 00/12/11-13:30:04 (suhler) * add class=props for automatic property extraction * * 1.31 00/11/28-10:09:34 (suhler) * changed log level * * 1.30 00/10/25-20:16:45 (suhler) * Session handling was broken: was not finding session id properly * . * * 1.29 00/10/20-15:42:31 (suhler) * api change due to race condition in TemplateRunner * * 1.28 00/07/05-14:11:11 (cstevens) * Server object is in RewriteContext used by templates, so templates (such as * the TclServerTemplate) can be initialized with the server and prefix, similar * to how Handlers are initialized. * * 1.27 00/05/31-13:50:06 (suhler) * use standard "null" session * * 1.26 00/05/22-14:06:02 (suhler) * add redirects and default handling ala the file handler * * 1.25 00/05/15-10:13:51 (suhler) * added diags * * 1.24 00/05/12-10:58:01 (suhler) * - removed all cookie header processing: * use a session handler instead * - Only filte system templates are supported. Removed uncocumented * "handler" option * - fixed documentation * * 1.23 00/04/17-14:25:23 (cstevens) * TemplateHandler.java, TemplateFilter.java: * 1. configuration property "templateClass" -> "templates" * * 1.22 00/03/29-16:15:53 (cstevens) * clean TemplateHandler. * * 1.21 00/02/01-19:01:27 (suhler) * missing close() * * 1.20 00/01/03-13:37:26 (suhler) * fixes * * 1.19 00/01/03-13:29:06 (suhler) * redo removing use of file handler * * 1.18 99/12/09-10:42:36 (suhler) * re-added old fileHandler hack * * 1.17 99/11/16-19:07:59 (cstevens) * wildcard imports * * 1.16 99/10/28-17:23:02 (cstevens) * ChangedTemplate * * 1.15 99/10/25-15:36:41 (cstevens) * propsPrefix to pref * * 1.14 99/10/21-18:14:10 (cstevens) * TemplateHandler now takes a list of Templates, rather than just one. When * parsing an HTML file, it will now dispatch to the union of all the tag * methods defined in the list of Templates. In that way, the user can * compose things like the BSLTemplate to iterate over request properties with * the PropsTemplate to substitute in their values. Otherwise, it would have * required N separate passes (via N separate TemplateHandlers) over the HTML * file, one for each Template and/or level of recursion in the BSLTemplate. * * 1.13 99/10/01-11:26:46 (cstevens) * Change logging to show prefix of Handler generating the log message. * * 1.12 99/09/29-16:05:54 (cstevens) * New HtmlRewriter object, that allows arbitrary rewriting of the HTML (by * templates and others), instead of forcing the templates to return a string * that contained all of the new HTML content in one big string. * * 1.11 99/06/28-10:50:07 (suhler) * ??? * * 1.10 99/05/24-18:24:46 (suhler) * fixed wildcard imports * * 1.9 99/05/24-17:38:07 (suhler) * rewritten to use new template class * (the timing is unfortunate) * * 1.8 99/05/13-09:22:28 (suhler) * modified to use new no-dilimeter option of HtmlMunge * * 1.7 99/03/30-09:30:59 (suhler) * documentation update * * 1.6 99/02/19-15:44:47 (suhler) * If the cookie name "none" is supplied as a property, then no cookies are * used, and every client gets the same object. * * 1.5 98/11/30-15:47:07 (suhler) * Rearranged code to permit subclassing for server-side xml processing * * 1.4 98/10/13-08:09:41 (suhler) * don't import self * * 1.3 98/09/21-14:55:04 (suhler) * changed the package names * * 1.2 98/09/20-15:38:14 (suhler) * removed *'s imports * * 1.2 98/09/14-18:03:12 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 3 2 handlers/templates/TemplateHandler.java * Name history : 2 1 handlers/TemplateHandler.java * Name history : 1 0 TemplateHandler.java * * 1.1 98/09/14-18:03:11 (suhler) * date and time created 98/09/14 18:03:11 by suhler * */ package sunlabs.brazil.template; import sunlabs.brazil.util.MatchString; import sunlabs.brazil.server.FileHandler; import sunlabs.brazil.server.Handler; import sunlabs.brazil.server.Request; import sunlabs.brazil.server.Server; import sunlabs.brazil.util.http.HttpUtil; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; /** * The TemplateHandler reads a template file from * the document root, based on the URL, and passes the content through * one or more template filters. *

* The following configuration parameters are used to initialize this * Handler:

*
prefix, suffix, glob, match *
Specify the URL that triggers this handler. * By default, all URL's are considered. * Only documents that are mime sub-types of text * are processed. See {@link sunlabs.brazil.server.FileHandler} for a * description of how to set mime types for url suffixes. * (See {@link MatchString}). *
templates *
A list of template names. For each name in the list, the property * name.class is examined to determine which class to * use for each template. Then name is used as the prefix * for other template specific properties if any. If * name.class does not exist, then name * is assumed to be the class name, and there are no template specific * properties for the template. Methods in the template classes will be * invoked to process the XML/HTML tags present in the content. *
session *
The name of the request property that the Session ID will be found * in, used to identify the proper template instance. * The default value is "SessionID". Typically, a sessionHandler, * such as {@link sunlabs.brazil.handler.CookieSessionHandler} is used * upstream to create the sessionID. If no id is found, then the * session named "common" is used instead. Exactly one instance of * each template class is created for each session. *
default *
The default file in the directory to use as a template if * a directory name is specified. Defaults to index[suffix], * or "index.html" if no suffix is provided. *
encoding *
The character encoding to use to interpret the template. * If no encoding is specified, the default encoding is used. * The template is read from the filesystem, and converted into a * String using this encoding. All template processing is done * using the String representation. *
outputEncoding *
The character encoding to use to interpret the template results. * If no "outputEncoding" is specified, then "encoding" is used. * Once template processing is complete, the results are converted * into a byte stream for transmission to the client using the * "outputEncoding", if specified. If not specified then the HTTP * default (8-bit ASCII) encoding is used. *
modified *
if present (e.g. set to any value) an HTTP * last-modified header is added to the response with the * current time. *
debug * if set to "true", template debugging is enabled: templates will emit * their pre-processed markup as an HTML comment. This parameter only takes effect * if the debug option is not specified for an individual template. *
*
tagPrefix *
If specified, all tag names defined for each template class * are prefixed with tagPrefix. * This parameter only takes effect * if the tagPrefix option is not specified for an individual template. *
*
*

The request properties DirectoryName, * fileName and lastModified may be * set as a convenience for downstream handlers. *

* This handler duplicates some of the functionality of the * {@link sunlabs.brazil.handler.DefaultFileHandler template filter}, * so that it may be used by itself in simple configurations. As such, * if issues re-directs if directories are given without a trailing "/", * and uses an "index" file (see default above) if a directory * name is specified. *

* To filter content other than from the file system, use the * {@link sunlabs.brazil.filter.TemplateFilter template filter} instead. * * @author Stephen Uhler (stephen.uhler@sun.com) * @author Colin Stevens (colin.stevens@sun.com) * @version %I% %E% */ public class TemplateHandler implements Handler { static final String HANDLER = "handler"; static final String TEMPLATES = "templates"; // the class to process these templates static final String SESSION = "session"; static final String ENCODING = "encoding"; static final String DEFAULT = "default"; // property for default document, given directory String propsPrefix; // our perfix in the config file String sessionProperty; // where to find the session name MatchString isMine; // check for matching url boolean modified; // if set, emit last-modified header String encoding; // the page character encoding Handler handler; TemplateRunner runner; // The template object for our class public boolean init(Server server, String propsPrefix) { this.propsPrefix = propsPrefix; isMine = new MatchString(propsPrefix, server.props); Properties props = server.props; sessionProperty = props.getProperty(propsPrefix + SESSION, "SessionID"); encoding = props.getProperty(propsPrefix + ENCODING); modified = (props.getProperty(propsPrefix + "modified") != null); /* * Gather the templates. */ String str; str = server.props.getProperty(propsPrefix + TEMPLATES); if (str == null) { server.log(Server.LOG_ERROR, propsPrefix, "no " + TEMPLATES + " property specified"); return false; } try { runner = new TemplateRunner(server, propsPrefix, str); } catch (ClassCastException e) { server.log(Server.LOG_ERROR, e.getMessage(), "not a Template"); return false; } catch (ClassNotFoundException e) { server.log(Server.LOG_ERROR, e.getMessage(), "unknown class"); return false; } return true; } /** * Process an html template file, using the supplied template * processing classes. */ public boolean respond(Request request) throws IOException { if (!isMine.match(request.url)) { return false; } Properties props = request.props; String root = props.getProperty(propsPrefix + FileHandler.ROOT, props.getProperty(FileHandler.ROOT, ".")); String name = FileHandler.urlToPath(request.url); File file = new File(root + name); String path = file.getPath(); String outputEncoding = props.getProperty(propsPrefix + "outputEncoding", encoding); /* * XXX This doesn't belong here. The DefaultFileHandler should * be used instead. This allows "simple" configurations using only * this handler. */ if (file.isDirectory()) { if (request.url.endsWith("/") == false) { request.redirect(request.url + "/", null); return true; } props.put("DirectoryName", path); String index = props.getProperty(propsPrefix+DEFAULT, "index.html"); file = new File(file, index); path = file.getPath(); } props.put("fileName", path); request.log(Server.LOG_DIAGNOSTIC, propsPrefix, "Looking for template file: " + path); int index = path.lastIndexOf('.'); if (index < 0) { request.log(Server.LOG_INFORMATIONAL, propsPrefix, "no file suffix for: " + path); return false; } String type = FileHandler.getMimeType(path, props, propsPrefix); if (type == null) { request.log(Server.LOG_INFORMATIONAL, propsPrefix, "unknown file suffix for: " + path); return false; } // make sure this is a text file! if (!type.toLowerCase().startsWith("text/")) { request.log(Server.LOG_INFORMATIONAL, propsPrefix, "Not a text file: " + type); return false; } // OK, now we can read in the file! String content; try { content = getContent(request, file, encoding); } catch (IOException e) { request.log(Server.LOG_WARNING, propsPrefix, e.getMessage()); return false; } String session = props.getProperty(sessionProperty, "common"); request.log(Server.LOG_DIAGNOSTIC, propsPrefix, "Using session (" + session + ")"); String result = runner.process(request, content, session); if (result != null) { if (modified) { request.addHeader("Last-Modified", HttpUtil.formatTime()); } FileHandler.setModified(request.props,file.lastModified()); if (outputEncoding == null) { request.sendResponse(result, type); } else { request.sendResponse(result.getBytes(outputEncoding), type + "; charset=" + outputEncoding); } return true; } else { request.log(Server.LOG_INFORMATIONAL,propsPrefix,runner.getError()); return false; } } /** * get the content associated with this template. * This version reads it from a file. * * @param request The standard request object * @param file The file object to get the template from * @return The content of the template to be processed */ public String getContent(Request request, File file, String encoding) throws IOException { FileInputStream in = new FileInputStream(file); byte[] buf = new byte[in.available()]; in.read(buf); in.close(); String result; if (encoding != null) { result = new String(buf, encoding); } else { result = new String(buf); } return result; } }