001: /*
002: * TclServerTemplate.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: 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.17
024: * Created by cstevens on 99/10/19
025: * Last modified by suhler on 00/12/11 13:33:12
026: */
027:
028: package sunlabs.brazil.tcl;
029:
030: import sunlabs.brazil.server.Request;
031: import sunlabs.brazil.server.Server;
032: import sunlabs.brazil.handler.ResourceHandler;
033: import sunlabs.brazil.template.RewriteContext;
034: import sunlabs.brazil.template.Template;
035:
036: import tcl.lang.Interp;
037: import tcl.lang.ReflectObject;
038: import tcl.lang.TCL;
039: import tcl.lang.TclException;
040: import tcl.lang.TclObject;
041: import tcl.lang.TclTemplateChannel;
042: import tcl.lang.TclUtil;
043:
044: import java.io.IOException;
045: import java.util.Properties;
046:
047: /**
048: * The <code>TclServerTemplate</code> looks for each
049: * <code><server language="tcl"></code>
050: * tag in an HTML page and treats the following data up to the next
051: * <code></server></code> tag as a Tcl script to evaluate.
052: * <p>
053: * The reason that Tcl scripts are included in an HTML page is usually
054: * to generate dynamic, server-side content. After running this template,
055: * everything between and including the <code><server></code> and
056: * <code></server></code> tags is replaced with the result of
057: * evaluating the Tcl script as follows: <ul>
058: * <li> Anything printed to the standard output of the Tcl interpreter is
059: * added to the HTML document (for instance, <code>puts "hello"</code>).
060: * <li> Additionally, if the Tcl script returns a value, that value is
061: * added to the HTML document (for instance, <code>return "bob"</code>).
062: * </ul>
063: * Multiple <code>puts</code> and a final <code>return</code> can both be
064: * used within a single Tcl fragment.
065: * <p>
066: * All Tcl fragments within a given page are evaluated in the same Tcl
067: * interpreter. The Tcl interpreter actually lives for the entire duration
068: * of this <code>Template</code> object, so the user can implement
069: * persistence across requests.
070: * <p>
071: * The following configuration parameters are used to initialize this
072: * template. <dl class=props>
073: * <dt> script
074: * <dd> The name of the Tcl script to evaluate when the interpreter is
075: * created. This script only evaluated when the interp is created,
076: * not on every request. The variables <code>prefix</code> and
077: * <code>server</code> are set before this file is evaluated, and
078: * are references to the parameters passed to a <code>handler</code>
079: * init method.
080: * <dt> root
081: * <dd> The document root, if the script is a relative file name.
082: * If the "root" property under the template prefix is not found, the
083: * global "root" property is used. If the global "root" property is
084: * not found, the current directory is used.
085: * <dt> debug
086: * <dd> If this configuration parameter is present, this class
087: * replaces the <code><server></code> and
088: * <code></server></code> tags with comments, so the user
089: * can keep track of where the dynamically generated content is coming
090: * from by examining the comments in the resultant HTML document.
091: * By default, the <code><server></code> and
092: * <code></server></code> are completely eliminated from the
093: * HTML document rather than changed into comments.
094: * </dl>
095: * <p>
096: * Before evaluating each HTML document, this class variables
097: * in the Tcl interpreter, which can be used to interact back with Java to
098: * do things like set the response headers: <dl>
099: * <dt> request
100: * <dd> Exposes the {@link sunlabs.brazil.server.Request} Java object.
101: * It is set anew at each request.
102: * <dt> prefix
103: * <dd> Exposes the handler prefix String.
104: * <dt> server
105: * <dd> Exposes the handler {@link sunlabs.brazil.server.Server} object.
106: * <dt> SessionId
107: * <dd> Exposes the session id for this interp, or "none".
108: * </dl>
109: *
110: * If a serialized version of this object is reconstituted, the init
111: * method must be called again.
112: *
113: * @author Colin Stevens (colin.stevens@sun.com)
114: * @version 1.17, 00/12/11
115: */
116: public class TclServerTemplate extends Template {
117: private static final String SCRIPT = "script";
118: private static final String DEBUG = "debug";
119:
120: Interp interp;
121:
122: /*
123: * In a template, transient variables don't last longer than the request.
124: */
125:
126: transient TclTemplateChannel chan;
127: transient TclObject request;
128: transient boolean debug;
129:
130: /**
131: * Called at the beginning of each HTML document that this
132: * <code>TclServerTemplate</code> is asked to process.
133: * <p>
134: * Redirects the standard output of the Tcl interpreter to the
135: * resultant HTML document and exposes the <code>Request</code>
136: * object as a Tcl variable.
137: * <p>
138: * The first time this method is called, the initialization script is
139: * sourced into the interpreter, based on the configuration properties
140: * in the <code>Request</code>
141: *
142: * @param hr
143: * The request and associated HTML document that will be
144: * processed.
145: *
146: * @returns <code>true</code> interpreter was successfully initialized
147: * <code>false</code> otherwise. About the only way that the
148: * initialization could fail would be <ol>
149: * <li> there was an error sourcing the initialization script.
150: * <li> if this class previously evaluated a Tcl fragment that
151: * redefined the scalar Tcl variables <code>request</code>
152: * or <code>server</code> as array variables.
153: * </ol>
154: * If <code>false</code> is returned, an error message is logged.
155: */
156: public boolean init(RewriteContext hr) {
157: Properties props = hr.request.props;
158:
159: if (interp == null) {
160: interp = new Interp();
161:
162: String script = props.getProperty(hr.prefix + SCRIPT);
163: String session = (hr.sessionId == null) ? "none"
164: : hr.sessionId;
165: try {
166: interp.eval("package require java");
167: interp.eval("set prefix " + hr.prefix);
168: interp.eval("set SessionId " + session);
169:
170: TclObject server = ReflectObject.newInstance(interp,
171: Server.class, hr.server);
172:
173: TclUtil.setVar(interp, "server", server, 0);
174:
175: if (script != null) {
176: String body = ResourceHandler.getResourceString(
177: props, hr.prefix, script);
178: interp.eval(body);
179: }
180: } catch (IOException e) {
181: hr.request.log(Server.LOG_ERROR, "reading init script",
182: script);
183: return false;
184: } catch (TclException e) {
185: if (e.getCompletionCode() != TCL.RETURN) {
186: hr.request.log(Server.LOG_ERROR,
187: "loading java package", e.toString());
188: return false;
189: }
190: }
191: }
192:
193: debug = (props.getProperty(hr.prefix + DEBUG) != null);
194:
195: try {
196: chan = new TclTemplateChannel(interp, hr);
197: request = ReflectObject.newInstance(interp, Request.class,
198: hr.request);
199: interp.eval("set request " + request);
200: } catch (TclException e) {
201: hr.request.log(Server.LOG_WARNING, hr.prefix,
202: "Setting up TCL environ: " + e.toString());
203: done(hr);
204: return false;
205: }
206: return true;
207: }
208:
209: /**
210: * Called after the HTML document has been processed.
211: * <p>
212: * Releases the resources allocated by <code>init</code>. This method
213: * should be called to ensure that the <code>HtmlRewriter</code> and
214: * all its attendant data structures are not preserved indefinitely
215: * (until the next request).
216: *
217: * @param hr
218: * The request and associated HTML document that was processed.
219: *
220: * @returns <code>true</code> always, indicating that this document was
221: * successfully processed to completion.
222: */
223: public boolean done(RewriteContext hr) {
224: if (chan != null) {
225: chan.unregister();
226: }
227: if (request != null) {
228: request.release();
229: }
230: chan = null;
231: request = null;
232: return true;
233: }
234:
235: /**
236: * Processes the <code><server></code> tag. Substitues the
237: * result of evaluating the following Tcl script into the resultant
238: * HTML document.
239: * <p>
240: * Note: Currently, there is no mechanism for other language interpreters
241: * to share the same <code>server</code> tag.
242: *
243: * @param hr
244: * The request and associated HTML document that will be
245: * processed.
246: */
247: public void tag_server(RewriteContext hr) {
248: String server = hr.getBody();
249: if ("tcl".equals(hr.get("language")) == false) {
250: return;
251: }
252: if (debug) {
253: hr.append("<!-- " + hr.getBody() + " -->\n");
254: }
255:
256: hr.accumulate(false);
257: hr.nextToken();
258: try {
259: interp.eval(hr.getBody());
260: } catch (TclException e) {
261: if (e.getCompletionCode() == TCL.RETURN) {
262: String result = interp.getResult().toString();
263: if (result.length() > 0) {
264: hr.append(result);
265: if (debug) {
266: hr.append("\n");
267: }
268: }
269: } else {
270: hr.append("\n<!-- server-side Tcl: error code "
271: + e.getCompletionCode() + ": "
272: + interp.getResult() + " -->\n");
273: if (debug) {
274: TclObject errorInfo;
275: try {
276: errorInfo = interp.getVar("errorInfo", null,
277: TCL.GLOBAL_ONLY);
278: } catch (Exception e2) {
279: errorInfo = null;
280: }
281: hr.append("\n<!-- server-side Tcl: error info: "
282: + errorInfo + " -->\n");
283: }
284: }
285: }
286:
287: hr.nextToken();
288: if (debug) {
289: hr.append("<!-- " + hr.getBody() + " -->");
290: }
291: hr.accumulate(true);
292: }
293: }
|