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.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 * <html>
72 * <body>
73 * <% 3.times { %>
74 * Hello World!
75 * <br>
76 *
77 * <% } %>
78 * </body>
79 * </html>
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("bindDefaultVariables", false);
93 * bindRequestParameters = init("bindRequestParameters", 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
118
119 this.servletContext = config.getServletContext();
120
121
122
123
124 String className = getClass().getName();
125 servletContext.log("Initializing on " + className + "...");
126
127
128
129
130 this.templateEngine = createTemplateEngine(config);
131 if (templateEngine == null) { throw new RuntimeException("Template engine not instantiated."); }
132
133
134
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
220
221 binding = new ServletBinding(request, response, servletContext);
222
223
224
225
226 setContentType(request, response);
227
228
229
230
231 Template template = handleRequest(request, response, binding);
232
233
234
235
236 merge(template, binding, response);
237
238 }
239 catch (Exception exception) {
240
241
242
243
244 error(request, response, exception);
245
246 }
247 finally {
248
249
250
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
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
317
318 String path = (String) request.getAttribute("javax.servlet.include.servlet_path");
319 if (path == null) {
320 path = request.getServletPath();
321 }
322
323
324
325
326 URL url = resolveTemplateName(path);
327 if (url == null) {
328 url = resolveTemplateName(request.getRequestURI());
329 }
330
331
332
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
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
360
361
362
363
364 URL url = servletContext.getResource(templateName);
365 if (url != null) { return url; }
366
367
368
369
370
371 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
372 url = classLoader.getResource(templateName);
373 if (url != null) { return url; }
374
375
376
377
378
379
380
381 url = getClass().getResource(templateName);
382 if (url != null) { return url; }
383
384
385
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
404
405
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
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
470
471 return;
472
473 }
474
475 }