1 package org.apache.turbine.services.velocity;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.ByteArrayOutputStream;
58 import java.io.IOException;
59 import java.io.OutputStream;
60 import java.io.OutputStreamWriter;
61 import java.io.Writer;
62 import java.util.Iterator;
63 import java.util.Vector;
64 import javax.servlet.ServletConfig;
65 import org.apache.commons.configuration.Configuration;
66 import org.apache.commons.configuration.ConfigurationConverter;
67 import org.apache.turbine.Turbine;
68 import org.apache.turbine.services.InitializationException;
69 import org.apache.turbine.services.pull.TurbinePull;
70 import org.apache.turbine.services.servlet.TurbineServlet;
71 import org.apache.turbine.services.template.BaseTemplateEngineService;
72 import org.apache.turbine.util.Log;
73 import org.apache.turbine.util.RunData;
74 import org.apache.turbine.util.StringUtils;
75 import org.apache.turbine.util.TurbineConfig;
76 import org.apache.turbine.util.TurbineException;
77 import org.apache.velocity.VelocityContext;
78 import org.apache.velocity.app.Velocity;
79 import org.apache.velocity.context.Context;
80
81
82 /***
83 * This is a Service that can process Velocity templates from within a
84 * Turbine Screen. Here's an example of how you might use it from a
85 * screen:<br>
86 *
87 * <code>
88 * Context context = TurbineVelocity.getContext(data);<br>
89 * context.put("message", "Hello from Turbine!");<br>
90 * String results = TurbineVelocity.handleRequest(context,"helloWorld.vm");<br>
91 * data.getPage().getBody().addElement(results);<br>
92 * </code>
93 *
94 * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
95 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
96 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
97 * @author <a href="mailto:sean@informage.ent">Sean Legassick</a>
98 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
99 * @version $Id: TurbineVelocityService.java,v 1.8 2002/07/11 16:53:23 mpoeschl Exp $
100 */
101 public class TurbineVelocityService extends BaseTemplateEngineService
102 implements VelocityService
103 {
104 /***
105 * The generic resource loader path property in velocity.
106 */
107 private static final String RESOURCE_LOADER_PATH =
108 ".resource.loader.path";
109
110 /***
111 * Default character set to use if not specified in the RunData object.
112 */
113 private static final String DEFAULT_CHAR_SET = "ISO-8859-1";
114
115 /***
116 * The prefix used for URIs which are of type <code>jar</code>.
117 */
118 private static final String JAR_PREFIX = "jar:";
119
120 /***
121 * The prefix used for URIs which are of type <code>absolute</code>.
122 */
123 private static final String ABSOLUTE_PREFIX = "file://";
124
125 /***
126 * The context used to the store the context
127 * containing the global application tools.
128 */
129 private Context globalContext = null;
130
131 /***
132 * Is the pullModelActive.
133 */
134 private boolean pullModelActive = false;
135
136 /***
137 * Should we refresh the tools on a per
138 * request basis. This is used for development.
139 */
140 private boolean refreshToolsPerRequest = false;
141
142 /***
143 * Performs early initialization of this Turbine service.
144 */
145 public void init(ServletConfig config) throws InitializationException
146 {
147 try
148 {
149 initVelocity();
150
151 globalContext = null;
152
153 /*
154 * We can only load the Pull Model ToolBox
155 * if the Pull service has been listed in the TR.props
156 * and the service has successfully been initialized.
157 */
158 if (TurbinePull.isRegistered())
159 {
160 globalContext = TurbinePull.getGlobalContext();
161
162 pullModelActive = true;
163
164 refreshToolsPerRequest = TurbinePull.refreshToolsPerRequest();
165 }
166
167 /*
168 * If the Pull service is inactive then we create
169 * an empty toolBoxContext
170 */
171 if (globalContext == null)
172 {
173 globalContext = new VelocityContext();
174 }
175
176 /*
177 * Register with the template service.
178 */
179 registerConfiguration("vm");
180 setInit(true);
181 }
182 catch (Exception e)
183 {
184 throw new InitializationException(
185 "Failed to initialize TurbineVelocityService", e);
186 }
187 }
188
189 /***
190 * Create a Context object that also contains the globalContext.
191 *
192 * @return A Context object.
193 */
194 public Context getContext()
195 {
196 return new VelocityContext(globalContext);
197 }
198
199 /***
200 * Create a Context from the RunData object. Adds a pointer to
201 * the RunData object to the WC so that RunData is available in
202 * the templates.
203 *
204 * @param data The Turbine RunData object.
205 * @return A clone of the WebContext needed by Velocity.
206 */
207 public Context getContext(RunData data)
208 {
209 /*
210 * Attempt to get it from the data first. If it doesn't
211 * exist, create it and then stuff it into the data.
212 */
213 Context context = (Context)
214 data.getTemplateInfo().getTemplateContext(VelocityService.CONTEXT);
215
216 if (context == null)
217 {
218 context = getContext();
219 context.put ( "data", data );
220
221 if (pullModelActive)
222 {
223 /*
224 * Populate the toolbox with request scope, session scope
225 * and persistent scope tools (global tools are already in
226 * the toolBoxContent which has been wrapped to construct
227 * this request-specific context).
228 */
229 TurbinePull.populateContext(context, data);
230 }
231
232 data.getTemplateInfo().setTemplateContext(
233 VelocityService.CONTEXT, context);
234 }
235 return context;
236 }
237
238 /***
239 * Process the request and fill in the template with the values
240 * you set in the Context.
241 *
242 * @param context The populated context.
243 * @param filename The file name of the template.
244 * @return The process template as a String.
245 *
246 * @throws TurbineException Any exception trown while processing will be
247 * wrapped into a TurbineException and rethrown.
248 */
249 public String handleRequest(Context context, String filename)
250 throws TurbineException
251 {
252 String results = null;
253 ByteArrayOutputStream bytes = null;
254
255 try
256 {
257 bytes = new ByteArrayOutputStream();
258 String charset = decodeRequest(context, filename, bytes);
259 results = bytes.toString(charset);
260 }
261 catch(Exception e)
262 {
263 renderingError(filename, e);
264 }
265 finally
266 {
267 try
268 {
269 if (bytes != null) bytes.close();
270 }
271 catch(IOException ignored)
272 {
273 }
274 }
275 return results;
276 }
277
278 /***
279 * Process the request and fill in the template with the values
280 * you set in the Context.
281 *
282 * @param context A Context.
283 * @param filename A String with the filename of the template.
284 * @param out A OutputStream where we will write the process template as
285 * a String.
286 *
287 * @throws TurbineException Any exception trown while processing will be
288 * wrapped into a TurbineException and rethrown.
289 */
290 public void handleRequest(Context context,
291 String filename,
292 OutputStream output)
293 throws TurbineException
294 {
295 decodeRequest(context, filename, output);
296 }
297
298 /***
299 * Process the request and fill in the template with the values
300 * you set in the Context.
301 *
302 * @param context A Context.
303 * @param filename A String with the filename of the template.
304 * @param writer A Writer where we will write the process template as
305 * a String.
306 *
307 * @throws TurbineException Any exception trown while processing will be
308 * wrapped into a TurbineException and rethrown.
309 */
310 public void handleRequest(Context context,
311 String filename,
312 Writer writer)
313 throws TurbineException
314 {
315 String encoding = getEncoding(context);
316 decodeRequest(context, filename, encoding, writer);
317 }
318
319
320 /***
321 * Process the request and fill in the template with the values
322 * you set in the Context. Apply the character and template
323 * encodings from RunData to the result.
324 *
325 * @param context A Context.
326 * @param filename A String with the filename of the template.
327 * @param out A OutputStream where we will write the process template as
328 * a String.
329 * @return The character encoding applied to the resulting String.
330 *
331 * @throws TurbineException Any exception trown while processing will be
332 * wrapped into a TurbineException and rethrown.
333 */
334 private String decodeRequest(Context context,
335 String filename,
336 OutputStream output)
337 throws TurbineException
338 {
339 /*
340 * This is for development.
341 */
342 if (pullModelActive && refreshToolsPerRequest)
343 {
344 TurbinePull.refreshGlobalTools();
345 }
346
347 /*
348 * Get the character and template encodings from the RunData object.
349 */
350 String charset = getCharSet(context);
351 String encoding = getEncoding(context);
352
353 OutputStreamWriter writer = null;
354
355 try
356 {
357 writer = new OutputStreamWriter(output, charset);
358 if (encoding != null)
359 {
360 Velocity.mergeTemplate(filename, encoding, context, writer);
361 }
362 else
363 {
364 Velocity.mergeTemplate(filename, context, writer);
365 }
366 }
367 catch(Exception e)
368 {
369 renderingError(filename, e);
370 }
371 finally
372 {
373 try
374 {
375 if (writer != null)
376 {
377 writer.flush();
378 }
379 }
380 catch (Exception ignored)
381 {
382 /*
383 * do nothing.
384 */
385 }
386 }
387
388 return charset;
389 }
390
391 /***
392 * Retrieve the required charset from the Turbine RunData in the context
393 *
394 * @param context A Context.
395 * @return The character set applied to the resulting String.
396 */
397 private String getCharSet(Context context)
398 {
399 String charset;
400 Object data = context.get("data");
401 if ((data != null) && (data instanceof RunData))
402 {
403 charset = ((RunData) data).getCharSet();
404 if (charset == null)
405 {
406 charset = DEFAULT_CHAR_SET;
407 }
408 }
409 else
410 {
411 charset = DEFAULT_CHAR_SET;
412 }
413
414 return charset;
415 }
416
417 /***
418 * Retrieve the required encoding from the Turbine RunData in the context
419 *
420 * @param context A Context.
421 * @return The encoding applied to the resulting String.
422 */
423 private String getEncoding(Context context)
424 {
425 String encoding;
426 Object data = context.get("data");
427 if ((data != null) && (data instanceof RunData))
428 {
429 encoding = ((RunData) data).getTemplateEncoding();
430 }
431 else
432 {
433 encoding = null;
434 }
435 return encoding;
436 }
437
438 /***
439 * Process the request and fill in the template with the values
440 * you set in the Context.
441 *
442 * @param context A Context.
443 * @param filename A String with the filename of the template.
444 * @param encoding The encoding to use with the template
445 * @param writer A Writer where we will write the process template as
446 * a String. This writer charset should be compatible with the selected
447 * encoding
448 *
449 * @throws TurbineException Any exception trown while processing will be
450 * wrapped into a TurbineException and rethrown.
451 */
452 private void decodeRequest(Context context,
453 String filename,
454 String encoding,
455 Writer writer)
456 throws TurbineException
457 {
458 /*
459 * This is for development.
460 */
461 if (pullModelActive && refreshToolsPerRequest)
462 {
463 TurbinePull.refreshGlobalTools();
464 }
465
466 try
467 {
468 if (encoding != null)
469 {
470 Velocity.mergeTemplate(filename, encoding, context, writer);
471 }
472 else
473 {
474 Velocity.mergeTemplate(filename, context, writer);
475 }
476 }
477 catch(Exception e)
478 {
479 renderingError(filename, e);
480 }
481 finally
482 {
483 try
484 {
485 if (writer != null)
486 {
487 writer.flush();
488 }
489 }
490 catch (Exception ignored)
491 {
492 /*
493 * do nothing.
494 */
495 }
496 }
497 }
498
499 /***
500 * Macro to handle rendering errors.
501 *
502 * @param filename The file name of the unrenderable template.
503 * @param e The error.
504 *
505 * @exception TurbineException Thrown every time. Adds additional
506 * information to <code>e</code>.
507 */
508 private static final void renderingError(String filename, Exception e)
509 throws TurbineException
510 {
511 String err = "Error rendering Velocity template: " + filename;
512 Log.error(err + ": " + e.getMessage());
513 throw new TurbineException(err, e);
514 }
515
516 /***
517 * Setup the velocity runtime by using a subset of the
518 * Turbine configuration which relates to velocity.
519 *
520 * @exception InitializationException For any errors during initialization.
521 */
522 private void initVelocity() throws InitializationException
523 {
524 /*
525 * Get the configuration for this service.
526 */
527 Configuration configuration = getConfiguration();
528
529 /*
530 * Now we have to perform a couple of path translations
531 * for our log file and template paths.
532 */
533 String path = Turbine.getRealPath
534 (configuration.getString(Velocity.RUNTIME_LOG, null));
535
536 if (StringUtils.isValid(path))
537 {
538 configuration.setProperty(Velocity.RUNTIME_LOG, path);
539 }
540 else
541 {
542 String msg = VelocityService.SERVICE_NAME + " runtime log file " +
543 "is misconfigured: '" + path + "' is not a valid log file";
544
545 if (TurbineServlet.getServletConfig() instanceof TurbineConfig)
546 {
547 msg += ": TurbineConfig users must use a path relative to " +
548 "web application root";
549 }
550 throw new Error(msg);
551 }
552
553 /*
554 * Get all the template paths where the velocity runtime should search
555 * for templates and collect them into a separate vector to avoid
556 * concurrent modification exceptions.
557 */
558 String key;
559 Vector keys = new Vector();
560 for (Iterator i = configuration.getKeys(); i.hasNext();)
561 {
562 key = (String) i.next();
563 if (key.endsWith(RESOURCE_LOADER_PATH))
564 {
565 keys.add(key);
566 }
567 }
568
569 /*
570 * Loop through all template paths, clear the corresponding
571 * velocity properties and translate them all to the webapp space.
572 */
573
574 int ind;
575 Vector paths;
576 String entry;
577 for (Iterator i = keys.iterator(); i.hasNext();)
578 {
579 key = (String) i.next();
580 paths = configuration.getVector(key,null);
581 if (paths != null)
582 {
583 Velocity.clearProperty(key);
584 configuration.clearProperty(key);
585
586 for (Iterator j = paths.iterator(); j.hasNext();)
587 {
588 path = (String) j.next();
589 if (path.startsWith(JAR_PREFIX + "file"))
590 {
591 /*
592 * A local jar resource URL path is a bit more
593 * complicated, but we can translate it as well.
594 */
595 ind = path.indexOf("!/");
596 if (ind >= 0)
597 {
598 entry = path.substring(ind);
599 path = path.substring(9,ind);
600 }
601 else
602 {
603 entry = "!/";
604 path = path.substring(9);
605 }
606 path = JAR_PREFIX + "file:" +
607 Turbine.getRealPath(path) + entry;
608 }
609 else if (path.startsWith(ABSOLUTE_PREFIX))
610 {
611 path = path.substring (ABSOLUTE_PREFIX.length(),
612 path.length());
613 }
614 else if (!path.startsWith(JAR_PREFIX))
615 {
616 // But we don't translate remote jar URLs.
617 path = Turbine.getRealPath(path);
618 }
619 // Put the translated paths back to the configuration.
620 configuration.addProperty(key,path);
621 }
622 }
623 }
624 try
625 {
626 Velocity.setExtendedProperties(ConfigurationConverter
627 .getExtendedProperties(configuration));
628 Velocity.init();
629 }
630 catch(Exception e)
631 {
632 /*
633 * This will be caught and rethrown by the init() method.
634 * Oh well, that will protect us from RuntimeException folk showing
635 * up somewhere above this try/catch
636 */
637 throw new InitializationException(
638 "Failed to set up TurbineVelocityService", e);
639 }
640 }
641
642 /***
643 * Find out if a given template exists. Velocity
644 * will do its own searching to determine whether
645 * a template exists or not.
646 *
647 * @param String template to search for
648 * @return boolean
649 */
650 public boolean templateExists(String template)
651 {
652 return Velocity.templateExists(template);
653 }
654
655 /***
656 * Performs post-request actions (releases context
657 * tools back to the object pool).
658 *
659 * @param context a Velocity Context
660 */
661 public void requestFinished(Context context)
662 {
663 if (pullModelActive)
664 {
665 TurbinePull.releaseTools(context);
666 }
667 }
668 }
This page was automatically generated by Maven