View Javadoc

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