View Javadoc

1   /*
2    * $Id: TemplateServlet.java,v 1.8 2004/05/22 06:11:00 spullara 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.lang.Binding;
42  import groovy.text.SimpleTemplateEngine;
43  import groovy.text.Template;
44  import groovy.text.TemplateEngine;
45  
46  import java.io.FileNotFoundException;
47  import java.io.IOException;
48  import java.net.URL;
49  import java.util.Enumeration;
50  import java.util.HashMap;
51  import java.util.Map;
52  
53  import javax.servlet.ServletConfig;
54  import javax.servlet.ServletContext;
55  import javax.servlet.ServletException;
56  import javax.servlet.http.HttpServlet;
57  import javax.servlet.http.HttpServletRequest;
58  import javax.servlet.http.HttpServletResponse;
59  
60  /***
61   * A generic servlet for templates.
62   * 
63   * It wraps a <code>groovy.text.TemplateEngine</code> to process HTTP
64   * requests. By default, it uses the
65   * <code>groovy.text.SimpleTemplateEngine</code> which interprets JSP-like (or
66   * Canvas-like) templates. <br>
67   * <br>
68   * 
69   * Example <code>HelloWorld.template</code>:
70   * 
71   * <pre><code>
72   * 
73   *  &lt;html&gt;
74   *  &lt;body&gt;
75   *  &lt;% 3.times { %&gt;
76   *  Hello World!
77   * <br>
78   * 
79   *  &lt;% } %&gt;
80   *  &lt;/body&gt;
81   *  &lt;/html&gt; 
82   *  
83   * </code></pre>
84   * 
85   * <br>
86   * <br>
87   * 
88   * Note: <br>
89   * Automatic binding of context variables and request (form) parameters is
90   * disabled by default. You can enable it by setting the servlet config init
91   * parameters to <code>true</code>.
92   * 
93   * <pre><code>
94   * bindDefaultVariables = init(&quot;bindDefaultVariables&quot;, false);
95   * bindRequestParameters = init(&quot;bindRequestParameters&quot;, false);
96   * </code></pre>
97   * 
98   * @author <a mailto:sormuras@web.de>Christian Stein </a>
99   * @version 1.3
100  */
101 public class TemplateServlet extends HttpServlet {
102 
103     public static final String DEFAULT_CONTENT_TYPE = "text/html";
104 
105     private ServletContext servletContext;
106 
107     protected TemplateEngine templateEngine;
108 
109     protected boolean bindDefaultVariables;
110 
111     protected boolean bindRequestParameters;
112 
113     /***
114      * Initializes the servlet.
115      * 
116      * @param config
117      *            Passed by the servlet container.
118      */
119     public void init(ServletConfig config) {
120 
121         /*
122          * Save the context.
123          */
124         this.servletContext = config.getServletContext();
125 
126         /*
127          * BEGIN
128          */
129         String className = getClass().getName();
130         servletContext.log("Initializing on " + className + "...");
131 
132         /*
133          * Configure from servlet config.
134          */
135         this.bindDefaultVariables = init(config, "bindDefaultVariables", false);
136         this.bindRequestParameters = init(config, "bindRequestParameters", false);
137 
138         /*
139          * Get TemplateEngine instance.
140          */
141         this.templateEngine = createTemplateEngine(config);
142         if (templateEngine == null) { throw new RuntimeException("Template engine not instantiated."); }
143 
144         /*
145          * END;
146          */
147         String engineName = templateEngine.getClass().getName();
148         servletContext.log(className + " initialized on " + engineName + ".");
149     }
150 
151     /***
152      * Convient evaluation of boolean configuration parameters.
153      * 
154      * @return <code>true</code> or <code>false</code>.
155      * @param config
156      *            Servlet configuration passed by the servlet container.
157      * @param param
158      *            Name of the paramter to look up.
159      * @param value
160      *            Default value if parameter name is not set.
161      */
162     protected boolean init(ServletConfig config, String param, boolean value) {
163         String string = config.getInitParameter(param);
164         if (string == null) { return value; }
165         return Boolean.valueOf(string).booleanValue();
166     }
167 
168     /***
169      * Creates the template engine.
170      * 
171      * Called by {@link #init(ServletConfig)} and returns just <code>
172      * SimpleTemplateEngine()</code> if the init parameter <code>templateEngine</code>
173      * is not set.
174      * 
175      * @return The underlying template engine.
176      * @param config
177      *            This serlvet configuration passed by the container.
178      * @see #createTemplateEngine()
179      */
180     protected TemplateEngine createTemplateEngine(ServletConfig config) {
181         String templateEngineClassName = config.getInitParameter("templateEngine");
182         if (templateEngineClassName == null) {
183             return new SimpleTemplateEngine();
184         }
185         try {
186             return (TemplateEngine) Class.forName(templateEngineClassName).newInstance();
187         } catch (InstantiationException e) {
188             servletContext.log("Could not instantiate template engine: " + templateEngineClassName, e);
189         } catch (IllegalAccessException e) {
190             servletContext.log("Could not access template engine class: " + templateEngineClassName, e);
191         } catch (ClassNotFoundException e) {
192             servletContext.log("Could not find template engine class: " + templateEngineClassName, e);
193        }
194         return null;
195     }
196 
197     /***
198      * Delegates to {@link #doRequest(HttpServletRequest, HttpServletResponse)}.
199      */
200     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
201         doRequest(request, response);
202     }
203 
204     /***
205      * Delegates to {@link #doRequest(HttpServletRequest, HttpServletResponse)}.
206      */
207     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
208         doRequest(request, response);
209     }
210 
211     /***
212      * Processes all requests by dispatching to helper methods.
213      * 
214      * TODO Outline the algorithm. Although the method names are well-chosen. :)
215      * 
216      * @param request
217      *            The http request.
218      * @param response
219      *            The http response.
220      * @throws ServletException
221      *             ...
222      * @throws IOException
223      *             ...
224      */
225     protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
226 
227         Binding binding = null;
228 
229         try {
230 
231             /*
232              * Create binding.
233              */
234             binding = createBinding(request, response);
235 
236             /*
237              * Set default content type.
238              */
239             setContentType(request, response);
240 
241             /*
242              * Create the template by its engine.
243              */
244             Template template = handleRequest(request, response, binding);
245 
246             /*
247              * Let the template, that is groovy, do the merge.
248              */
249             merge(template, binding, response);
250 
251         }
252         catch (Exception exception) {
253 
254             /*
255              * Call exception handling hook.
256              */
257             error(request, response, exception);
258 
259         }
260         finally {
261 
262             /*
263              * Indicate we are finally done with this request.
264              */
265             requestDone(request, response, binding);
266 
267         }
268 
269     }
270 
271     /***
272      * Creates the application context.
273      * 
274      * Sets 5 variables if and only if <code>bindDefaultParameters</code> is
275      * <code>true</code>:
276      * 
277      * <pre><code>
278      * binding.setVariable(&quot;request&quot;, request);
279      * binding.setVariable(&quot;response&quot;, response);
280      * binding.setVariable(&quot;context&quot;, servletContext);
281      * binding.setVariable(&quot;session&quot;, request.getSession(true));
282      * binding.setVariable(&quot;out&quot;, response.getWriter());
283      * </code></pre>
284      * 
285      * Binds all form parameters, too. This is, where we leave the clean MVC
286      * pattern and Velocity behind. (...) Nobody told you to quit Velocity
287      * anyway. :)
288      * 
289      * @return Groovy Binding also known as application context.
290      * @param request
291      *            The HTTP request.
292      * @param response
293      *            The HTTP response.
294      * @throws Exception
295      *             Any exception.
296      */
297     protected Binding createBinding(HttpServletRequest request, HttpServletResponse response) throws Exception {
298 
299         /*
300          * Create empty binding.
301          */
302         Binding binding = new Binding();
303 
304         /*
305          * Bind default variables.
306          */
307         if (bindDefaultVariables) {
308             binding.setVariable("request", request);
309             binding.setVariable("response", response);
310             binding.setVariable("context", servletContext);
311             binding.setVariable("session", request.getSession(true));
312             binding.setVariable("out", response.getWriter());
313         }
314 
315         /*
316          * Bind form (aka request) parameters.
317          */
318         if (bindRequestParameters) {
319             Enumeration parameterNames = request.getParameterNames();
320             while (parameterNames.hasMoreElements()) {
321                 String key = (String) parameterNames.nextElement();
322                 if (binding.getVariables().containsKey(key)) {
323                     servletContext.log("Key \"" + key + "\" already bound.");
324                     continue;
325                 }
326                 String[] values = request.getParameterValues(key);
327                 if (values.length == 1) {
328                     binding.setVariable(key, values[0]);
329                 }
330                 else {
331                     binding.setVariable(key, values);
332                 }
333             }
334         }
335 
336         return binding;
337 
338     }
339 
340     /***
341      * Sets {@link #DEFAULT_CONTENT_TYPE}.
342      * 
343      * @param request
344      *            The HTTP request.
345      * @param response
346      *            The HTTP response.
347      */
348     protected void setContentType(HttpServletRequest request, HttpServletResponse response) {
349 
350         response.setContentType(DEFAULT_CONTENT_TYPE);
351 
352     }
353 
354     /***
355      * Default request handling. <br>
356      * 
357      * Leaving Velocity behind again. The template, actually the Groovy code in
358      * it, could handle/process the entire request. Good or not? This depends on
359      * you! :)<br>
360      * 
361      * Anyway, here no exception is thrown -- but it's strongly recommended to
362      * override this method in derived class and do the real processing against
363      * the model inside it. The template should be used, like Velocity
364      * templates, to produce the view, the html page. Again, it's up to you!
365      * 
366      * @return The template that will be merged.
367      * @param request
368      *            The HTTP request.
369      * @param response
370      *            The HTTP response.
371      * @param binding
372      *            The application context.
373      * @throws Exception
374      */
375     protected Template handleRequest(
376             HttpServletRequest request,
377             HttpServletResponse response,
378             Binding binding)
379     throws Exception {
380         /*
381          * Delegate to getTemplate(String).
382          */
383         return getTemplate(request);
384     }
385 
386     /***
387      * Gets the template by its name.
388      * 
389      * @return The template that will be merged.
390      * @param templateName
391      *            The name of the template.
392      * @throws Exception
393      *             Any exception.
394      */
395     protected Template getTemplate(HttpServletRequest request) throws Exception {
396 
397         /*
398          * If its an include we need to get the included path, not the main request path.
399          */
400         String path = (String) request.getAttribute("javax.servlet.include.servlet_path");
401         if (path == null) {
402             path = request.getServletPath();
403         }
404 
405         /*
406          * Delegate to resolveTemplateName(String). Twice if necessary.
407          */
408         URL url = resolveTemplateName(path);
409         if (url == null) {
410             url = resolveTemplateName(request.getRequestURI());
411         }
412 
413         /*
414          * Template not found?
415          */
416         if (url == null) {
417             String uri = request.getRequestURI();
418             servletContext.log("Resource \"" + uri + "\" not found.");
419             throw new FileNotFoundException(uri);
420         }
421 
422         /*
423          * Delegate to getTemplate(URL).
424          */
425         return getTemplate(url);
426 
427     }
428 
429     /***
430      * Locate template and convert its location to an URL.
431      * 
432      * @return The URL pointing to the resource... the template.
433      * @param templateName
434      *            The name of the template.
435      * @throws Exception
436      *             Any exception.
437      */
438     protected URL resolveTemplateName(String templateName) throws Exception {
439 
440         /*
441          * Try servlet context resource facility.
442          * 
443          * Good for names pointing to templates relatively to the servlet
444          * context.
445          */
446         URL url = servletContext.getResource(templateName);
447         if (url != null) { return url; }
448 
449         /*
450          * Precedence: Context classloader, Class classloader
451          * (those classloaders will delegate to the system classloader)
452          */
453         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
454         url = classLoader.getResource(templateName);
455         if (url != null) { return url; }
456 
457         /*
458          * Try the class loader, that loaded this class.
459          * 
460          * Good for templates located within the class path.
461          * 
462          */
463         url = getClass().getResource(templateName);
464         if (url != null) { return url; }
465 
466         /*
467          * Still, still here? Just return null.
468          */
469         return null;
470 
471     }
472 
473     /***
474      * Gets the template by its url.
475      * 
476      * @return The template that will be merged.
477      * @param templateURL
478      *            The url of the template.
479      * @throws Exception
480      *             Any exception.
481      */
482     protected Template getTemplate(URL templateURL) throws Exception {
483 
484         /*
485          * Let the engine create the template from given URL.
486          * 
487          * TODO Is createTemplate(Reader); faster? Fail safer?
488          */
489         return templateEngine.createTemplate(templateURL);
490 
491     }
492 
493     /***
494      * Merges the template and writes response.
495      * 
496      * @param template
497      *            The template that will be merged... now!
498      * @param binding
499      *            The application context.
500      * @param response
501      *            The HTTP response.
502      * @throws Exception
503      *             Any exception.
504      */
505     protected void merge(Template template, Binding binding, HttpServletResponse response) throws Exception {
506 
507         /*
508          * Set binding and write response.
509          */
510         template.make(binding.getVariables()).writeTo(response.getWriter());
511 
512     }
513 
514     /***
515      * Simply sends an internal server error page (code 500).
516      * 
517      * @param request
518      *            The HTTP request.
519      * @param response
520      *            The HTTP response.
521      * @param exception
522      *            The cause.
523      */
524     protected void error(HttpServletRequest request, HttpServletResponse response, Exception exception) {
525 
526         try {
527             response.sendError(500, exception.getMessage());
528         }
529         catch (IOException ioException) {
530             servletContext.log("Should not happen.", ioException);
531         }
532 
533     }
534 
535     /***
536      * Called one request is processed.
537      * 
538      * This clean-up hook is always called, even if there was an exception
539      * flying around and the error method was executed.
540      * 
541      * @param request
542      *            The HTTP request.
543      * @param response
544      *            The HTTP response.
545      * @param binding
546      *            The application context.
547      */
548     protected void requestDone(HttpServletRequest request, HttpServletResponse response, Binding binding) {
549 
550         /*
551          * Nothing to clean up.
552          */
553         return;
554 
555     }
556 
557 }