KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > groovy > servlet > TemplateServlet


1 /*
2  * $Id: TemplateServlet.java,v 1.16 2005/06/05 08:16:07 cstein Exp $
3  *
4  * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5  *
6  * Redistribution and use of this software and associated documentation
7  * ("Software"), with or without modification, are permitted provided that the
8  * following conditions are met:
9  *
10  * 1. Redistributions of source code must retain copyright statements and
11  * notices. Redistributions must also contain a copy of this document.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright notice,
14  * this list of conditions and the following disclaimer in the documentation
15  * and/or other materials provided with the distribution.
16  *
17  * 3. The name "groovy" must not be used to endorse or promote products derived
18  * from this Software without prior written permission of The Codehaus. For
19  * written permission, please contact info@codehaus.org.
20  *
21  * 4. Products derived from this Software may not be called "groovy" nor may
22  * "groovy" appear in their names without prior written permission of The
23  * Codehaus. "groovy" is a registered trademark of The Codehaus.
24  *
25  * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
28  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30  * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
31  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  *
38  */

39 package groovy.servlet;
40
41 import groovy.text.SimpleTemplateEngine;
42 import groovy.text.Template;
43 import groovy.text.TemplateEngine;
44
45 import java.io.BufferedReader JavaDoc;
46 import java.io.File JavaDoc;
47 import java.io.FileReader JavaDoc;
48 import java.io.IOException JavaDoc;
49 import java.io.StringReader JavaDoc;
50 import java.io.Writer JavaDoc;
51 import java.util.Map JavaDoc;
52 import java.util.WeakHashMap JavaDoc;
53
54 import javax.servlet.ServletConfig JavaDoc;
55 import javax.servlet.ServletException JavaDoc;
56 import javax.servlet.http.HttpServletRequest JavaDoc;
57 import javax.servlet.http.HttpServletResponse JavaDoc;
58
59 /**
60  * A generic servlet for serving (mostly HTML) templates.
61  *
62  * It wraps a <code>groovy.text.TemplateEngine</code> to process HTTP
63  * requests. By default, it uses the
64  * <code>groovy.text.SimpleTemplateEngine</code> which interprets JSP-like (or
65  * Canvas-like) templates. The init parameter <code>templateEngine</code>
66  * defines the fully qualified class name of the template to use.<br>
67  *
68  * <p>
69  * Headless <code>helloworld.html</code> example
70  * <pre><code>
71  * &lt;html&gt;
72  * &lt;body&gt;
73  * &lt;% 3.times { %&gt;
74  * Hello World!
75  * &lt;% } %&gt;
76  * &lt;br&gt;
77  * session.id = ${session.id}
78  * &lt;/body&gt;
79  * &lt;/html&gt;
80  * </code></pre>
81  * </p>
82  *
83  * @see TemplateServlet#setVariables(ServletBinding)
84  *
85  * @author Christian Stein
86  * @author Guillaume Laforge
87  * @version 2.0
88  */

89 public class TemplateServlet extends AbstractHttpServlet {
90
91   /**
92    * Simple cache entry that validates against last modified and length
93    * attributes of the specified file.
94    *
95    * @author Sormuras
96    */

97   private static class TemplateCacheEntry {
98
99     long lastModified;
100     long length;
101     Template template;
102
103     public TemplateCacheEntry(File JavaDoc file, Template template) {
104       if (file == null) {
105         throw new NullPointerException JavaDoc("file");
106       }
107       if (template == null) {
108         throw new NullPointerException JavaDoc("template");
109       }
110       this.lastModified = file.lastModified();
111       this.length = file.length();
112       this.template = template;
113     }
114
115     /**
116      * Checks the passed file attributes against those cached ones.
117      *
118      * @param file
119      * Other file handle to compare to the cached values.
120      * @return <code>true</code> if all measured values match, else <code>false</code>
121      */

122     public boolean validate(File JavaDoc file) {
123       if (file == null) {
124         throw new NullPointerException JavaDoc("file");
125       }
126       if (file.lastModified() != this.lastModified) {
127         return false;
128       }
129       if (file.length() != this.length) {
130         return false;
131       }
132       return true;
133     }
134
135   }
136
137   /*
138    * Enables more log statements.
139    */

140   private static final boolean VERBOSE = true;
141
142   /**
143    * Simple file name to template cache map.
144    */

145   // Java5 private final Map<String, TemplateCacheEntry> cache;
146
private final Map JavaDoc cache;
147
148   /**
149    * Underlying template engine used to evaluate template source files.
150    */

151   private TemplateEngine engine;
152
153   /**
154    * Flag that controls the appending of the "Generated by ..." comment.
155    */

156   private boolean generatedBy;
157
158   /**
159    * Create new TemplateSerlvet.
160    */

161   public TemplateServlet() {
162     // Java 5 this.cache = new WeakHashMap<String, TemplateCacheEntry>();
163
this.cache = new WeakHashMap JavaDoc();
164     //this.context = null; // assigned later by super.init()
165
this.engine = null; // assigned later by init()
166
this.generatedBy = true; // may be changed by init()
167
}
168
169   /**
170    * Triggers the template creation eliminating all new line characters.
171    *
172    * Its a work around! New lines should cause troubles when compiling. But
173    * sometimes(?) the do: http://jira.codehaus.org/browse/GROOVY-818
174    * See FIXME note around line 250, where this method is called.
175    *
176    * @see TemplateServlet#getTemplate(File)
177    * @see BufferedReader#readLine()
178    */

179   private Template createTemplate(int bufferCapacity, FileReader JavaDoc fileReader)
180       throws Exception JavaDoc {
181     StringBuffer JavaDoc sb = new StringBuffer JavaDoc(bufferCapacity);
182     BufferedReader JavaDoc reader = new BufferedReader JavaDoc(fileReader);
183     try {
184       String JavaDoc line = reader.readLine();
185       while (line != null) {
186         sb.append(line);
187         //if (VERBOSE) { // prints the entire source file
188
// log(" | " + line);
189
//}
190
line = reader.readLine();
191       }
192     }
193     finally {
194       if (reader != null) {
195         reader.close();
196       }
197     }
198     StringReader JavaDoc stringReader = new StringReader JavaDoc(sb.toString());
199     Template template = engine.createTemplate(stringReader);
200     stringReader.close();
201     return template;
202   }
203
204   /**
205    * Gets the template created by the underlying engine parsing the request.
206    *
207    * <p>
208    * This method looks up a simple (weak) hash map for an existing template
209    * object that matches the source file. If the source file didn't change in
210    * length and its last modified stamp hasn't changed compared to a precompiled
211    * template object, this template is used. Otherwise, there is no or an
212    * invalid template object cache entry, a new one is created by the underlying
213    * template engine. This new instance is put to the cache for consecutive
214    * calls.
215    * </p>
216    *
217    * @return The template that will produce the response text.
218    * @param file
219    * The HttpServletRequest.
220    * @throws IOException
221    * If the request specified an invalid template source file
222    */

223   protected Template getTemplate(File JavaDoc file) throws ServletException JavaDoc {
224
225     String JavaDoc key = file.getAbsolutePath();
226     Template template = null;
227
228     //
229
// Test cache for a valid template bound to the key.
230
//
231
TemplateCacheEntry entry = (TemplateCacheEntry) cache.get(key);
232     if (entry != null) {
233       if (entry.validate(file)) { // log("Valid cache hit! :)");
234
template = entry.template;
235       } // else log("Cached template needs recompiliation!");
236
} // else log("Cache miss.");
237

238     //
239
// Template not cached or the source file changed - compile new template!
240
//
241
if (template == null) {
242       if (VERBOSE) {
243         log("Creating new template from file " + file + "...");
244       }
245       FileReader JavaDoc reader = null;
246       try {
247         reader = new FileReader JavaDoc(file);
248         //
249
// FIXME Template creation should eliminate '\n' by default?!
250
//
251
// template = engine.createTemplate(reader);
252
//
253
// General error during parsing:
254
// expecting anything but ''\n''; got it anyway
255
//
256
template = createTemplate((int) file.length(), reader);
257       }
258       catch (Exception JavaDoc e) {
259         throw new ServletException JavaDoc("Creation of template failed: " + e, e);
260       }
261       finally {
262         if (reader != null) {
263           try {
264             reader.close();
265           }
266           catch (IOException JavaDoc ignore) {
267             // e.printStackTrace();
268
}
269         }
270       }
271       cache.put(key, new TemplateCacheEntry(file, template));
272       if (VERBOSE) {
273         log("Created and added template to cache. [key=" + key + "]");
274       }
275     }
276
277     //
278
// Last sanity check.
279
//
280
if (template == null) {
281       throw new ServletException JavaDoc("Template is null? Should not happen here!");
282     }
283
284     return template;
285
286   }
287
288   /**
289    * Initializes the servlet from hints the container passes.
290    * <p>
291    * Delegates to sub-init methods and parses the following parameters:
292    * <ul>
293    * <li> <tt>"generatedBy"</tt> : boolean, appends "Generated by ..." to the
294    * HTML response text generated by this servlet.
295    * </li>
296    * </ul>
297    * @param config
298    * Passed by the servlet container.
299    * @throws ServletException
300    * if this method encountered difficulties
301    *
302    * @see TemplateServlet#initTemplateEngine(ServletConfig)
303    */

304   public void init(ServletConfig JavaDoc config) throws ServletException JavaDoc {
305     super.init(config);
306     this.engine = initTemplateEngine(config);
307     if (engine == null) {
308       throw new ServletException JavaDoc("Template engine not instantiated.");
309     }
310     String JavaDoc value = config.getInitParameter("generatedBy");
311     if (value != null) {
312       this.generatedBy = Boolean.valueOf(value).booleanValue();
313     }
314     if (VERBOSE) {
315       log(getClass().getName() + " initialized on " + engine.getClass());
316     }
317   }
318
319   /**
320    * Creates the template engine.
321    *
322    * Called by {@link TemplateServlet#init(ServletConfig)} and returns just
323    * <code>new groovy.text.SimpleTemplateEngine()</code> if the init parameter
324    * <code>templateEngine</code> is not set by the container configuration.
325    *
326    * @param config
327    * Current serlvet configuration passed by the container.
328    *
329    * @return The underlying template engine or <code>null</code> on error.
330    *
331    * @see TemplateServlet#initTemplateEngine(javax.servlet.ServletConfig)
332    */

333   protected TemplateEngine initTemplateEngine(ServletConfig JavaDoc config) {
334     String JavaDoc name = config.getInitParameter("templateEngine");
335     if (name == null) {
336       return new SimpleTemplateEngine();
337     }
338     try {
339       return (TemplateEngine) Class.forName(name).newInstance();
340     }
341     catch (InstantiationException JavaDoc e) {
342       log("Could not instantiate template engine: " + name, e);
343     }
344     catch (IllegalAccessException JavaDoc e) {
345       log("Could not access template engine class: " + name, e);
346     }
347     catch (ClassNotFoundException JavaDoc e) {
348       log("Could not find template engine class: " + name, e);
349     }
350     return null;
351   }
352
353   /**
354    * Services the request with a response.
355    * <p>
356    * First the request is parsed for the source file uri. If the specified file
357    * could not be found or can not be read an error message is sent as response.
358    *
359    * </p>
360    * @param request
361    * The http request.
362    * @param response
363    * The http response.
364    * @throws IOException
365    * if an input or output error occurs while the servlet is
366    * handling the HTTP request
367    * @throws ServletException
368    * if the HTTP request cannot be handled
369    */

370   public void service(HttpServletRequest JavaDoc request,
371       HttpServletResponse JavaDoc response) throws ServletException JavaDoc, IOException JavaDoc {
372
373     if (VERBOSE) {
374       log("Creating/getting cached template...");
375     }
376
377     //
378
// Get the template source file handle.
379
//
380
File JavaDoc file = super.getScriptUriAsFile(request);
381     if (!file.exists()) {
382       response.sendError(HttpServletResponse.SC_NOT_FOUND);
383       return; // throw new IOException(file.getAbsolutePath());
384
}
385     if (!file.canRead()) {
386       response.sendError(HttpServletResponse.SC_FORBIDDEN, "Can not read!");
387       return; // throw new IOException(file.getAbsolutePath());
388
}
389
390     //
391
// Get the requested template.
392
//
393
long getMillis = System.currentTimeMillis();
394     Template template = getTemplate(file);
395     getMillis = System.currentTimeMillis() - getMillis;
396
397     //
398
// Create new binding for the current request.
399
//
400
ServletBinding binding = new ServletBinding(request, response, servletContext);
401     setVariables(binding);
402
403     //
404
// Prepare the response buffer content type _before_ getting the writer.
405
//
406
response.setContentType(CONTENT_TYPE_TEXT_HTML);
407
408     //
409
// Get the output stream writer from the binding.
410
//
411
Writer JavaDoc out = (Writer JavaDoc) binding.getVariable("out");
412     if (out == null) {
413       out = response.getWriter();
414     }
415
416     //
417
// Evaluate the template.
418
//
419
if (VERBOSE) {
420       log("Making template...");
421     }
422     // String made = template.make(binding.getVariables()).toString();
423
// log(" = " + made);
424
long makeMillis = System.currentTimeMillis();
425     template.make(binding.getVariables()).writeTo(out);
426     makeMillis = System.currentTimeMillis() - makeMillis;
427
428     if (generatedBy) {
429       StringBuffer JavaDoc sb = new StringBuffer JavaDoc(100);
430       sb.append("\n<!-- Generated by Groovy TemplateServlet [create/get=");
431       sb.append(Long.toString(getMillis));
432       sb.append(" ms, make=");
433       sb.append(Long.toString(makeMillis));
434       sb.append(" ms] -->\n");
435       out.write(sb.toString());
436     }
437
438     //
439
// Set status code and flush the response buffer.
440
//
441
response.setStatus(HttpServletResponse.SC_OK);
442     response.flushBuffer();
443
444     if (VERBOSE) {
445       log("Template request responded. [create/get=" + getMillis
446           + " ms, make=" + makeMillis + " ms]");
447     }
448
449   }
450
451   /**
452    * Override this method to set your variables to the Groovy binding.
453    * <p>
454    * All variables bound the binding are passed to the template source text,
455    * e.g. the HTML file, when the template is merged.
456    * </p>
457    * <p>
458    * The binding provided by TemplateServlet does already include some default
459    * variables. As of this writing, they are (copied from
460    * {@link groovy.servlet.ServletBinding}):
461    * <ul>
462    * <li><tt>"request"</tt> : HttpServletRequest </li>
463    * <li><tt>"response"</tt> : HttpServletResponse </li>
464    * <li><tt>"context"</tt> : ServletContext </li>
465    * <li><tt>"application"</tt> : ServletContext </li>
466    * <li><tt>"session"</tt> : request.getSession(true) </li>
467    * </ul>
468    * </p>
469    * <p>
470    * And via explicit hard-coded keywords:
471    * <ul>
472    * <li><tt>"out"</tt> : response.getWriter() </li>
473    * <li><tt>"sout"</tt> : response.getOutputStream() </li>
474    * <li><tt>"html"</tt> : new MarkupBuilder(response.getWriter()) </li>
475    * </ul>
476    * </p>
477    *
478    * <p>Example binding all servlet context variables:
479    * <pre><code>
480    * class Mytlet extends TemplateServlet {
481    *
482    * private ServletContext context;
483    *
484    * public void init(ServletConfig config) {
485    * this.context = config.getServletContext();
486    * }
487    *
488    * protected void setVariables(ServletBinding binding) {
489    * Enumeration enumeration = context.getAttributeNames();
490    * while (enumeration.hasMoreElements()) {
491    * String name = (String) enumeration.nextElement();
492    * binding.setVariable(name, context.getAttribute(name));
493    * }
494    * }
495    *
496    * }
497    * <code></pre>
498    * </p>
499    *
500    * @param binding
501    * to get modified
502    *
503    * @see TemplateServlet
504    */

505   protected void setVariables(ServletBinding binding) {
506     // empty
507
}
508
509 }
510
Popular Tags