001: /*
002: * TemplateRunner.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, suhler.
022: *
023: * Version: 1.25
024: * Created by suhler on 99/05/06
025: * Last modified by suhler on 00/12/11 13:30:24
026: */
027:
028: package sunlabs.brazil.template;
029:
030: import sunlabs.brazil.server.Request;
031: import sunlabs.brazil.server.Server;
032: import sunlabs.brazil.session.SessionManager;
033: import sunlabs.brazil.util.regexp.Regexp;
034: import sunlabs.brazil.util.regexp.Regsub;
035: import sunlabs.brazil.util.LexHTML;
036:
037: import java.lang.reflect.InvocationTargetException;
038: import java.lang.reflect.Method;
039: import java.util.Hashtable;
040: import java.util.Vector;
041: import java.util.StringTokenizer;
042:
043: /**
044: * Class for processing html templates.
045: * An html template is an ordinary html string, with additional
046: * application specific tags added (sort of like XML). Each
047: * tag is mapped to a java method of a template class, that rewrites its
048: * tag into normal html.
049: * <p>
050: * The mechanism used to map templates into sessions is inadaquate, and
051: * should be fixed in a future version.
052: * The mechanism used to map templates into sessions is inadaquate, and
053: * should be fixed in a future version. In the current implementation,
054: * Each session maintains its own set of template instances. Instance
055: * variables in template classes may be used to hold session specific
056: * state. Calls to a template are synchronized on the session id;
057: * only one request per session is dealt with simultaneously.
058: */
059:
060: public class TemplateRunner {
061: static final String TEMPLATE = "template";
062:
063: int tagsProcessed = 0;
064: String error = null; // The last error
065:
066: Class[] types; // Types of templates.
067: Hashtable dispatch; // Map: tag -> class index, method.
068:
069: private static final Regexp hex = new Regexp("_x..");
070: private static final Regexp.Filter hexFilter = new Regexp.Filter() {
071: public boolean filter(Regsub rs, StringBuffer sb) {
072: String match = rs.matched();
073: int hi = Character.digit(match.charAt(1), 16);
074: int lo = Character.digit(match.charAt(2), 16);
075: sb.append((char) ((hi << 4) | lo));
076:
077: return true;
078: }
079: };
080:
081: private static class Dispatch {
082: int index;
083: Method method;
084:
085: public Dispatch(int index, Method method) {
086: this .index = index;
087: this .method = method;
088: }
089: }
090:
091: /**
092: * Process an HTML template with a template processing class.
093: * We peruse the template class, looking at all of its methods.
094: * When when we process a template, we match the html tags against
095: * the declared methods in the template class. Each
096: * method name of the form <code>tag_<i>xxx</i></code> or
097: * <code>tag_slash_<i>xxx</i></code> is invoked when the corrosponding
098: * <i><xxx></i> or <i></xxx></i> tag is found.
099: * <p>
100: * The methods <code>init</code> and <code>done</code> are each called
101: * once, at the beginning and end of the page respectively. These methods
102: * are called for all templates, in the order they are specified in
103: * the <i>templates</i> parameter.
104: * <p>
105: * There are three methods taht may be defined that don't follow the naming
106: * convension described above. They are:
107: * <ul>
108: * <li><code>comment</code><br>
109: * is called for each html/XML comment.
110: * <li><code>string</code><br>
111: * is called for all text between any tags.
112: * <li><code>defaultTag</code><br>
113: * is called for every tag that does not specifically have a tag method.
114: * If more than one template defines one of these methods, only the
115: * first template's method will be called.
116: * </ul>
117: * <p>
118: * @param names
119: * The names of the Template classes. This TemplateRunner will
120: * dispatch to the methods described by the union of all the
121: * tag methods in the given Template classes.
122: * <p>
123: * The <code>init</code> and <code>done</code> methods for each
124: * template specified will be called in order. If any of
125: * the calls returns <code>false</code>, this handler terminates
126: * and no output is generated.
127: * <p>
128: * The names "comment", "string", and "defaultTag" are
129: * handled specially.
130: */
131: public TemplateRunner(String names) throws ClassNotFoundException,
132: ClassCastException {
133: dispatch = new Hashtable();
134:
135: Vector types = new Vector();
136: int count = 0;
137:
138: StringTokenizer st = new StringTokenizer(names);
139: while (st.hasMoreTokens()) {
140: String temName = st.nextToken();
141: // System.out.println("Processing template: " + temName);
142: Class temType = Class.forName(temName);
143:
144: if (Template.class.isAssignableFrom(temType) == false) {
145: throw new ClassCastException(temType.getName());
146: }
147:
148: types.addElement(temType);
149:
150: Method[] methods = temType.getMethods();
151: for (int i = 0; i < methods.length; i++) {
152: Method method = methods[i];
153: String name = method.getName();
154: if (name.equals("comment")
155: && dispatch.get(name) == null) {
156: dispatch.put(name, new Dispatch(count, method));
157: // System.out.println("Found comment in: " + temName);
158: continue;
159: }
160: if (name.equals("string") && dispatch.get(name) == null) {
161: dispatch.put(name, new Dispatch(count, method));
162: // System.out.println("Found string in: " + temName);
163: continue;
164: }
165: if (name.equals("defaultTag")
166: && dispatch.get(name) == null) {
167: dispatch.put(name, new Dispatch(count, method));
168: // System.out.println("Found defaultTag in: " + temName);
169: continue;
170: }
171: if (name.startsWith("tag_") == false) {
172: continue;
173: }
174: name = name.substring(4);
175: if (name.startsWith("slash_")) {
176: name = "/" + name.substring(6);
177: }
178: name = hex.sub(name, hexFilter);
179:
180: if (dispatch.get(name) == null) {
181: dispatch.put(name, new Dispatch(count, method));
182: }
183: }
184: count++;
185: }
186:
187: types.copyInto(this .types = new Class[count]);
188: }
189:
190: /**
191: * Process an html template file, using the supplied template processing
192: * Return the content of the template just processed, or null if
193: * there was no template processed.
194: *
195: * @param content
196: * The template.
197: *
198: * @param sessionId
199: * An arbitrary identifier used to locate the correct instance
200: * of the template class for processing this template. The
201: * first time an identifier is used, a new instance is created.
202: *
203: * @param args
204: * The arguments passed to the templates init method.
205: *
206: * @return content or null
207: */
208: public String process(Server server, String prefix,
209: Request request, String content, String sessionId) {
210: tagsProcessed = 0;
211: // error = null;
212:
213: /*
214: * Now look up the session object. If this is a new session, then we
215: * get a new object, otherwise we get the last one we used for this
216: * session
217: */
218:
219: Vector templates = (Vector) SessionManager.getSession(
220: sessionId, prefix + TEMPLATE, Vector.class);
221:
222: synchronized (templates) {
223: if (templates.size() != types.length) {
224: templates.setSize(0);
225: for (int i = 0; i < types.length; i++) {
226: try {
227: templates.addElement(types[i].newInstance());
228: } catch (Exception e) {
229: }
230: }
231: }
232: RewriteContext hr = new RewriteContext(server, prefix,
233: request, content, sessionId, this , templates);
234:
235: /*
236: * Call the init() method of all the Templates.
237: */
238: for (int i = 0; i < types.length; i++) {
239: Template obj = (Template) templates.elementAt(i);
240: if (obj.init(hr) == false) {
241: error = types[i] + " " + request.url
242: + ": init Rejecting request";
243: return null;
244: }
245: }
246:
247: /*
248: * Process the document.
249: */
250: while (hr.nextToken()) {
251: process(hr);
252: }
253:
254: /*
255: * Call the done() method of all the Templates.
256: */
257: for (int i = 0; i < templates.size(); i++) {
258: Template obj = (Template) templates.elementAt(i);
259: if (obj.done(hr) == false) {
260: error = types[i] + " " + request.url
261: + ": done rejecting request";
262: return null;
263: }
264: }
265: return hr.toString();
266: }
267: }
268:
269: /**
270: * Processes the next token in the HTML document represented by the
271: * given <code>RewriteContext</code>. Processing a token involves either
272: * dispatching to a tag-handling method in one of the
273: * <code>Template</code> objects, or just advancing to the next token
274: * if no <code>Template</code> was interested.
275: *
276: * @param hr
277: * The RewriteContext holding the state of the HTML document.
278: */
279: public void process(RewriteContext hr) {
280: switch (hr.getType()) {
281: case LexHTML.COMMENT:
282: case LexHTML.STRING:
283: case LexHTML.TAG: {
284: String tag;
285: if (hr.getType() == LexHTML.COMMENT) {
286: tag = "comment";
287: // System.out.println("(comment)");
288: } else if (hr.getType() == LexHTML.STRING) {
289: tag = "string";
290: } else {
291: tag = hr.getTag();
292: }
293: Dispatch d = (Dispatch) dispatch.get(tag);
294: if (hr.getType() == LexHTML.TAG && d == null) {
295: d = (Dispatch) dispatch.get("defaultTag");
296: }
297: if (d != null) {
298: Template obj = (Template) hr.templates
299: .elementAt(d.index);
300: try {
301: d.method.invoke(obj, hr.args);
302: tagsProcessed++;
303: } catch (InvocationTargetException e) {
304: hr.append("<!-- " + tag + ": "
305: + e.getTargetException() + " -->");
306: e.getTargetException().printStackTrace();
307: } catch (Exception e) {
308: hr.append("<!-- " + tag + ": " + e + " -->");
309: e.printStackTrace();
310: }
311: }
312: }
313: }
314: }
315:
316: /**
317: * Return the last error message generated, or null of no
318: * errors have occurred since the last call to "process".
319: * XXX not thread safe between calls to process() and getError().
320: */
321:
322: public String getError() {
323: return error;
324: }
325:
326: /**
327: * Return the # of tags replaced in the previous call to "process".
328: */
329:
330: public int tagsProcessed() {
331: return tagsProcessed;
332: }
333: }
|