/* * TemplateRunner.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 1999-2008 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.5 * Created by suhler on 99/05/06 * Last modified by suhler on 08/03/04 10:56:59 * * Version Histories: * * 2.5 08/03/04-10:56:59 (suhler) * javadoc fixes * * 2.4 06/08/01-14:07:44 (suhler) * added templateFromTag() to allow greater introspection of our template classes * minor doc fixes * . * * 2.3 03/08/01-16:19:00 (suhler) * fixes for javadoc * * 2.2 03/07/09-12:27:29 (suhler) * added tagPrefix methods * * 2.1 02/10/01-16:36:48 (suhler) * version change * * 1.34 02/07/24-10:47:07 (suhler) * doc updates * * 1.33 02/07/19-15:10:01 (suhler) * added (experimental) tagPrefix option to handle XML namespaces * * 1.32 02/06/24-15:39:47 (suhler) * Better diagnostics for templates without a public constructor * * 1.31 02/05/01-11:28:06 (suhler) * fix sccs version info * * 1.30 01/10/03-13:54:03 (suhler) * added "tags seen" for diagnostic purposes * * 1.29 01/08/03-18:13:04 (suhler) * remove white space from class names * * 1.28 01/05/11-14:52:22 (suhler) * Make sure templates are Assignable from "TemplateInterface" instead * of "Template", so templates don't have to extend Template * * 1.27 01/05/02-15:33:49 (drach) * Add template tokens. * * 1.26 01/04/09-18:36:24 (suhler) * fixed bug in * _xx * * 1.25 00/12/11-13:30:24 (suhler) * doc typo * * 1.24 00/11/21-13:24:21 (suhler) * better error messages * * 1.23 00/10/31-10:19:33 (suhler) * doc fixes * * 1.22 00/10/20-15:43:17 (suhler) * Removed getContent() due to race condition. process() now returns * the content. * * 1.21 00/10/16-13:34:36 (suhler) * Added 2 more methods: * "string" dispatches on all text between tags * "defaultTag" dispatches on all tags that aren't otherwised matched * . * * 1.20 00/07/06-15:59:26 (suhler) * doc fixes * * 1.19 00/07/05-14:11:19 (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.18 00/05/31-13:50:18 (suhler) * name change, docs * * 1.17 00/04/12-15:55:34 (cstevens) * Work with SerialPersist SessionManager * * 1.16 00/02/11-12:44:52 (suhler) * handle comments * * 1.15 99/11/30-09:47:57 (suhler) * remove diagnostics * * 1.14 99/11/16-19:08:11 (cstevens) * wildcard imports. * * 1.13 99/10/28-17:23:17 (cstevens) * ChangedTemplate * * 1.12 99/10/25-15:37:49 (cstevens) * working on templates with symbolic names. * * 1.11 99/10/21-18:14:30 (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.10 99/10/14-14:57:26 (cstevens) * resolve wilcard imports. * * 1.9 99/10/06-12:18:48 (suhler) * * 1.8 99/09/29-16:05:59 (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.7 99/08/04-18:44:11 (suhler) * I forget, sorry * * 1.6 99/06/28-10:51:10 (suhler) * separate template processing class * * 1.5 99/05/25-09:21:21 (suhler) * modified to use new HtmlMunger * * 1.4 99/05/24-17:38:51 (suhler) * Changed to use new HtmlMunger. I already did this change once, but * it got lost somehow ??? * * 1.3 99/05/13-09:23:00 (suhler) * ?? * * 1.2 99/05/06-15:22:53 (suhler) * added the ability to use a mapping table * * 1.2 99/05/06-14:12:25 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 2 1 handlers/templates/TemplateRunner.java * Name history : 1 0 handlers/Template.java * * 1.1 99/05/06-14:12:24 (suhler) * date and time created 99/05/06 14:12:24 by suhler * */ package sunlabs.brazil.template; import sunlabs.brazil.server.Request; import sunlabs.brazil.server.Server; import sunlabs.brazil.session.SessionManager; import sunlabs.brazil.util.regexp.Regexp; import sunlabs.brazil.util.regexp.Regsub; import sunlabs.brazil.util.LexHTML; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Vector; import java.util.StringTokenizer; /** * Class for processing html templates. * An html template is an ordinary html string, with additional * application specific tags added (sort of like XML). Each * tag is mapped to a java method of a template class, that rewrites its * tag into normal html. *
* The mechanism used to map templates into sessions is inadaquate, and
* should be fixed in a future version. In the current implementation,
* Each session maintains its own set of template instances. Instance
* variables in template classes may be used to hold session specific
* state. Calls to a template are synchronized on the session id;
* only one request per session is dealt with simultaneously.
*
* @author Colin Stevens
* @author Stephen Uhler
* @version %W
*/
public class TemplateRunner
{
static final String TAG_PREFIX = "tagPrefix";
static final String TEMPLATE = "template";
private Server server;
private String prefix; // prefix of the invoker
private int tagsProcessed = 0;
private int tagsSeen = 0;
private String error = null; // The last error
private Class[] types; // Types of templates.
private String[] prefixes; // Template specific prefixes
private Hashtable dispatch; // Map: tag -> class index, method.
private static final Regexp hex = new Regexp("_x[0-9a-zA-Z][0-9a-zA-Z]");
private static final Regexp.Filter hexFilter = new Regexp.Filter() {
public boolean filter(Regsub rs, StringBuffer sb) {
String match = rs.matched();
int hi = Character.digit(match.charAt(2), 16);
int lo = Character.digit(match.charAt(3), 16);
sb.append((char) ((hi << 4) | lo));
return true;
}
};
private static class Dispatch {
int index;
Method method;
String prefix;
public Dispatch(int index, Method method, String prefix) {
this.index = index;
this.method = method;
this.prefix = prefix;
}
}
/**
* Process an HTML template with a template processing class.
* We peruse the template class, looking at all of its methods.
* When when we process a template, we match the html tags against
* the declared methods in the template class. Each
* method name of the form tag_xxx
or
* tag_slash_xxx
is invoked when the corrosponding
* <xxx> or </xxx> tag is found.
*
* Each instance of _xnn
in the method name
* is replaced by the corrosponding hex code for the character.
* This allows non-printable tags to to be processed with templates.
*
* The methods init
and done
are each called
* once, at the beginning and end of the page respectively. These methods
* are called for all templates, in the order they are specified in
* the templates parameter.
*
* There are three methods that may be defined that don't follow the naming * convention described above. They are: *
comment
string
defaultTag
* If the server property "tagPrefix" associated with each template's * properties prefix exists, it is used to prefix each tag name * (this feature is for experimental support of XML namespaces, * and probably doesn't belong here). *
* @param server
* The HTTP server that created the Handler
or
* Filter
that invoked this
* TemplateRunner
* @param prefix
* The prefix associated with the parent Handler
* or Filter
* @param names
* The names of the Template classes or property prefixes (i.e.
* tokens) that, when concatenated with ".class" define a property
* that names a Template class. This TemplateRunner will
* dispatch to the methods described by the union of all the
* tag methods in the given Template classes.
*
* The init
and done
methods for each
* template specified will be called in order. If any of
* the calls returns false
, this handler terminates
* and no output is generated.
*
* The names "comment", "string", and "defaultTag" are
* handled specially.
*/
public
TemplateRunner(Server server, String prefix, String names)
throws ClassNotFoundException, ClassCastException
{
this.server = server;
this.prefix = prefix;
dispatch = new Hashtable();
Vector types = new Vector();
Vector prefixes = new Vector();
int count = 0;
StringTokenizer st = new StringTokenizer(names);
while (st.hasMoreTokens()) {
String temPrefix = null;
String temName = st.nextToken();
// System.out.println("Processing template: " + temName);
String className = server.props.getProperty(temName + ".class");
if (className == null) {
className = temName;
temPrefix = prefix;
} else {
temPrefix = temName + ".";
}
Class temType = Class.forName(className.trim());
if (TemplateInterface.class.isAssignableFrom(temType) == false) {
throw new ClassCastException(temType.getName());
}
types.addElement(temType);
prefixes.addElement(temPrefix);
Method[] methods = temType.getMethods();
String tagPrefix = server.props.getProperty(temPrefix + TAG_PREFIX,
server.props.getProperty(prefix + TAG_PREFIX, ""));
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
String name = method.getName();
if (name.equals("comment") && dispatch.get(name) == null) {
dispatch.put(name, new Dispatch(count, method, temPrefix));
// System.out.println("Found comment in: " + temName);
continue;
}
if (name.equals("string") && dispatch.get(name) == null) {
dispatch.put(name, new Dispatch(count, method, temPrefix));
// System.out.println("Found string in: " + temName);
continue;
}
if (name.equals("defaultTag") && dispatch.get(name) == null) {
dispatch.put(name, new Dispatch(count, method, temPrefix));
// System.out.println("Found defaultTag in: " + temName);
continue;
}
if (name.startsWith("tag_") == false) {
continue;
}
name = name.substring(4);
if (name.startsWith("slash_")) {
name = "/" + tagPrefix + name.substring(6);
} else {
name = tagPrefix + name;
}
name = hex.sub(name, hexFilter);
if (dispatch.get(name) == null) {
dispatch.put(name, new Dispatch(count, method, temPrefix));
}
}
count++;
}
types.copyInto(this.types = new Class[count]);
prefixes.copyInto(this.prefixes = new String[count]);
}
/**
* Process an html template file, using the supplied template processing
* Return the content of the template just processed, or null if
* there was no template processed.
*
* @param request
* The HTTP request
* @param content
* The template.
*
* @param sessionId
* An arbitrary identifier used to locate the correct instance
* of the template class for processing this template. The
* first time an identifier is used, a new instance is created.
*
* @return content or null
*/
public String
process(Request request, String content, String sessionId)
{
tagsProcessed = 0;
tagsSeen = 0;
// error = null;
/*
* Now look up the session object. If this is a new session, then we
* get a new object, otherwise we get the last one we used for this
* session
*/
Vector templates = (Vector) SessionManager.getSession(sessionId,
prefix + TEMPLATE, Vector.class);
synchronized (templates) {
if (templates.size() != types.length) {
templates.setSize(0);
for (int i = 0; i < types.length; i++) {
try {
templates.addElement(types[i].newInstance());
} catch (Exception e) {
throw new ClassCastException("Missing constructor " +
types[i].getName() + "()");
}
}
}
RewriteContext hr = new RewriteContext(server, prefix, request,
content, sessionId, this, templates);
/*
* Call the init() method of all the Templates.
*/
for (int i = 0; i < types.length; i++) {
Template obj = (Template) templates.elementAt(i);
hr.prefix = prefixes[i]; // see discussion in RewriteContext
if (obj.init(hr) == false) {
error = types[i] + " " + request.url +
": init Rejecting request";
return null;
}
}
/*
* Process the document.
*/
while (hr.nextToken()) {
process(hr);
}
/*
* Call the done() method of all the Templates.
*/
for (int i = 0; i < templates.size(); i++) {
Template obj = (Template) templates.elementAt(i);
hr.prefix = prefixes[i]; // see discussion in RewriteContext
if (obj.done(hr) == false) {
error = types[i] + " " + request.url +
": done rejecting request";
return null;
}
}
return hr.toString();
}
}
/**
* Processes the next token in the HTML document represented by the
* given RewriteContext
. Processing a token involves either
* dispatching to a tag-handling method in one of the
* Template
objects, or just advancing to the next token
* if no Template
was interested.
*
* @param hr
* The RewriteContext holding the state of the HTML document.
*/
public void
process(RewriteContext hr)
{
switch (hr.getType()) {
case LexHTML.COMMENT:
case LexHTML.STRING:
case LexHTML.TAG: {
String tag;
if (hr.getType() == LexHTML.COMMENT) {
tag = "comment";
// System.out.println("(comment)");
} else if (hr.getType() == LexHTML.STRING) {
tag = "string";
} else {
tag = hr.getTag();
tagsSeen++;
}
Dispatch d = (Dispatch) dispatch.get(tag);
if (hr.getType() == LexHTML.TAG && d == null) {
d = (Dispatch) dispatch.get("defaultTag");
}
if (d != null) {
Template obj = (Template) hr.templates.elementAt(d.index);
// see discussion in RewriteContext regarding prefix
hr.prefix = d.prefix;
try {
d.method.invoke(obj, hr.args);
tagsProcessed++;
} catch (InvocationTargetException e) {
hr.append("");
e.getTargetException().printStackTrace();
} catch (Exception e) {
hr.append("");
e.printStackTrace();
}
}
}
}
}
/**
* Return the last error message generated, or null of no
* errors have occurred since the last call to "process".
* XXX not thread safe between calls to process() and getError().
*/
public String
getError() {
return error;
}
/**
* Return the # of HTML tags seen in the previous call to "process".
*/
public int
tagsSeen() {
return tagsSeen;
}
/**
* Return the # of tags replaced in the previous call to "process".
*/
public int
tagsProcessed() {
return tagsProcessed;
}
/**
* Return the object instance of the template that will process this
* tag (if any). This allows templates to cooperate with each other.
* If you need to use this method, then one of us did something wrong.
*/
protected Template templateFromTag(RewriteContext hr, String tag) {
Template template = null;
Dispatch d = (Dispatch) dispatch.get(tag);
if (d != null) {
template = (Template) hr.templates.elementAt(d.index);
}
return template;
}
}