1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 * <html>
74 * <body>
75 * <% 3.times { %>
76 * Hello World!
77 * <br>
78 *
79 * <% } %>
80 * </body>
81 * </html>
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("bindDefaultVariables", false);
95 * bindRequestParameters = init("bindRequestParameters", 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
123
124 this.servletContext = config.getServletContext();
125
126
127
128
129 String className = getClass().getName();
130 servletContext.log("Initializing on " + className + "...");
131
132
133
134
135 this.bindDefaultVariables = init(config, "bindDefaultVariables", false);
136 this.bindRequestParameters = init(config, "bindRequestParameters", false);
137
138
139
140
141 this.templateEngine = createTemplateEngine(config);
142 if (templateEngine == null) { throw new RuntimeException("Template engine not instantiated."); }
143
144
145
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
233
234 binding = createBinding(request, response);
235
236
237
238
239 setContentType(request, response);
240
241
242
243
244 Template template = handleRequest(request, response, binding);
245
246
247
248
249 merge(template, binding, response);
250
251 }
252 catch (Exception exception) {
253
254
255
256
257 error(request, response, exception);
258
259 }
260 finally {
261
262
263
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("request", request);
279 * binding.setVariable("response", response);
280 * binding.setVariable("context", servletContext);
281 * binding.setVariable("session", request.getSession(true));
282 * binding.setVariable("out", 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
301
302 Binding binding = new Binding();
303
304
305
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
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
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
399
400 String path = (String) request.getAttribute("javax.servlet.include.servlet_path");
401 if (path == null) {
402 path = request.getServletPath();
403 }
404
405
406
407
408 URL url = resolveTemplateName(path);
409 if (url == null) {
410 url = resolveTemplateName(request.getRequestURI());
411 }
412
413
414
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
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
442
443
444
445
446 URL url = servletContext.getResource(templateName);
447 if (url != null) { return url; }
448
449
450
451
452
453 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
454 url = classLoader.getResource(templateName);
455 if (url != null) { return url; }
456
457
458
459
460
461
462
463 url = getClass().getResource(templateName);
464 if (url != null) { return url; }
465
466
467
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
486
487
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
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
552
553 return;
554
555 }
556
557 }