1 package org.apache.velocity;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.UnsupportedEncodingException;
27 import java.io.Writer;
28 import java.util.List;
29
30 import org.apache.velocity.context.Context;
31 import org.apache.velocity.context.InternalContextAdapterImpl;
32 import org.apache.velocity.exception.MethodInvocationException;
33 import org.apache.velocity.exception.ParseErrorException;
34 import org.apache.velocity.exception.ResourceNotFoundException;
35 import org.apache.velocity.exception.TemplateInitException;
36 import org.apache.velocity.exception.VelocityException;
37 import org.apache.velocity.runtime.RuntimeConstants;
38 import org.apache.velocity.runtime.directive.Scope;
39 import org.apache.velocity.runtime.directive.StopCommand;
40 import org.apache.velocity.runtime.parser.ParseException;
41 import org.apache.velocity.runtime.parser.node.SimpleNode;
42 import org.apache.velocity.runtime.resource.Resource;
43 import org.apache.velocity.runtime.resource.ResourceManager;
44
45 /**
46 * This class is used for controlling all template
47 * operations. This class uses a parser created
48 * by JavaCC to create an AST that is subsequently
49 * traversed by a Visitor.
50 *
51 * <pre>
52 * // set up and initialize Velocity before this code block
53 *
54 * Template template = Velocity.getTemplate("test.wm");
55 * Context context = new VelocityContext();
56 *
57 * context.put("foo", "bar");
58 * context.put("customer", new Customer());
59 *
60 * template.merge(context, writer);
61 * </pre>
62 *
63 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
64 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
65 * @version $Id: Template.java 778045 2009-05-23 22:17:46Z nbubna $
66 */
67 public class Template extends Resource
68 {
69 /*
70 * The name of the variable to use when placing
71 * the scope object into the context.
72 */
73 private String scopeName = "template";
74 private boolean provideScope = false;
75
76 private VelocityException errorCondition = null;
77
78 /** Default constructor */
79 public Template()
80 {
81 super();
82
83 setType(ResourceManager.RESOURCE_TEMPLATE);
84 }
85
86 /**
87 * gets the named resource as a stream, parses and inits
88 *
89 * @return true if successful
90 * @throws ResourceNotFoundException if template not found
91 * from any available source.
92 * @throws ParseErrorException if template cannot be parsed due
93 * to syntax (or other) error.
94 * @throws IOException problem reading input stream
95 */
96 public boolean process()
97 throws ResourceNotFoundException, ParseErrorException
98 {
99 data = null;
100 InputStream is = null;
101 errorCondition = null;
102
103 /*
104 * first, try to get the stream from the loader
105 */
106 try
107 {
108 is = resourceLoader.getResourceStream(name);
109 }
110 catch( ResourceNotFoundException rnfe )
111 {
112 /*
113 * remember and re-throw
114 */
115
116 errorCondition = rnfe;
117 throw rnfe;
118 }
119
120 /*
121 * if that worked, lets protect in case a loader impl
122 * forgets to throw a proper exception
123 */
124
125 if (is != null)
126 {
127 /*
128 * now parse the template
129 */
130
131 try
132 {
133 BufferedReader br = new BufferedReader( new InputStreamReader( is, encoding ) );
134 data = rsvc.parse( br, name);
135 initDocument();
136 return true;
137 }
138 catch( UnsupportedEncodingException uce )
139 {
140 String msg = "Template.process : Unsupported input encoding : " + encoding
141 + " for template " + name;
142
143 errorCondition = new ParseErrorException( msg );
144 throw errorCondition;
145 }
146 catch ( ParseException pex )
147 {
148 /*
149 * remember the error and convert
150 */
151 errorCondition = new ParseErrorException(pex, name);
152 throw errorCondition;
153 }
154 catch ( TemplateInitException pex )
155 {
156 errorCondition = new ParseErrorException( pex, name);
157 throw errorCondition;
158 }
159 /**
160 * pass through runtime exceptions
161 */
162 catch( RuntimeException e )
163 {
164 errorCondition = new VelocityException("Exception thrown processing Template "
165 +getName(), e);
166 throw errorCondition;
167 }
168 finally
169 {
170 /*
171 * Make sure to close the inputstream when we are done.
172 */
173 try
174 {
175 is.close();
176 }
177 catch(IOException e)
178 {
179 // If we are already throwing an exception then we want the original
180 // exception to be continued to be thrown, otherwise, throw a new Exception.
181 if (errorCondition == null)
182 {
183 throw new VelocityException(e);
184 }
185 }
186 }
187 }
188 else
189 {
190 /*
191 * is == null, therefore we have some kind of file issue
192 */
193 errorCondition = new ResourceNotFoundException("Unknown resource error for resource " + name );
194 throw errorCondition;
195 }
196 }
197
198 /**
199 * initializes the document. init() is not longer
200 * dependant upon context, but we need to let the
201 * init() carry the template name down throught for VM
202 * namespace features
203 * @throws TemplateInitException When a problem occurs during the document initialization.
204 */
205 public void initDocument()
206 throws TemplateInitException
207 {
208 /*
209 * send an empty InternalContextAdapter down into the AST to initialize it
210 */
211
212 InternalContextAdapterImpl ica = new InternalContextAdapterImpl( new VelocityContext() );
213
214 try
215 {
216 /*
217 * put the current template name on the stack
218 */
219
220 ica.pushCurrentTemplateName( name );
221 ica.setCurrentResource( this );
222
223 /*
224 * init the AST
225 */
226
227 ((SimpleNode)data).init( ica, rsvc);
228
229 String property = scopeName+'.'+RuntimeConstants.PROVIDE_SCOPE_CONTROL;
230 provideScope = rsvc.getBoolean(property, provideScope);
231 }
232 finally
233 {
234 /*
235 * in case something blows up...
236 * pull it off for completeness
237 */
238
239 ica.popCurrentTemplateName();
240 ica.setCurrentResource( null );
241 }
242
243 }
244
245 /**
246 * The AST node structure is merged with the
247 * context to produce the final output.
248 *
249 * @param context Conext with data elements accessed by template
250 * @param writer output writer for rendered template
251 * @throws ResourceNotFoundException if template not found
252 * from any available source.
253 * @throws ParseErrorException if template cannot be parsed due
254 * to syntax (or other) error.
255 * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
256 */
257 public void merge( Context context, Writer writer)
258 throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
259 {
260 merge(context, writer, null);
261 }
262
263
264 /**
265 * The AST node structure is merged with the
266 * context to produce the final output.
267 *
268 * @param context Conext with data elements accessed by template
269 * @param writer output writer for rendered template
270 * @param macroLibraries a list of template files containing macros to be used when merging
271 * @throws ResourceNotFoundException if template not found
272 * from any available source.
273 * @throws ParseErrorException if template cannot be parsed due
274 * to syntax (or other) error.
275 * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
276 * @since 1.6
277 */
278 public void merge( Context context, Writer writer, List macroLibraries)
279 throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
280 {
281 /*
282 * we shouldn't have to do this, as if there is an error condition,
283 * the application code should never get a reference to the
284 * Template
285 */
286
287 if (errorCondition != null)
288 {
289 throw errorCondition;
290 }
291
292 if( data != null)
293 {
294 /*
295 * create an InternalContextAdapter to carry the user Context down
296 * into the rendering engine. Set the template name and render()
297 */
298
299 InternalContextAdapterImpl ica = new InternalContextAdapterImpl( context );
300
301 /**
302 * Set the macro libraries
303 */
304 ica.setMacroLibraries(macroLibraries);
305
306 if (macroLibraries != null)
307 {
308 for (int i = 0; i < macroLibraries.size(); i++)
309 {
310 /**
311 * Build the macro library
312 */
313 try
314 {
315 rsvc.getTemplate((String) macroLibraries.get(i));
316 }
317 catch (ResourceNotFoundException re)
318 {
319 /*
320 * the macro lib wasn't found. Note it and throw
321 */
322 rsvc.getLog().error("template.merge(): " +
323 "cannot find template " +
324 (String) macroLibraries.get(i));
325 throw re;
326 }
327 catch (ParseErrorException pe)
328 {
329 /*
330 * the macro lib was found, but didn't parse - syntax error
331 * note it and throw
332 */
333 rsvc.getLog().error("template.merge(): " +
334 "syntax error in template " +
335 (String) macroLibraries.get(i) + ".");
336 throw pe;
337 }
338
339 catch (Exception e)
340 {
341 throw new RuntimeException("Template.merge(): parse failed in template " +
342 (String) macroLibraries.get(i) + ".", e);
343 }
344 }
345 }
346
347 if (provideScope)
348 {
349 ica.put(scopeName, new Scope(this, ica.get(scopeName)));
350 }
351 try
352 {
353 ica.pushCurrentTemplateName( name );
354 ica.setCurrentResource( this );
355
356 ( (SimpleNode) data ).render( ica, writer);
357 }
358 catch (StopCommand stop)
359 {
360 if (!stop.isFor(this))
361 {
362 throw stop;
363 }
364 else if (rsvc.getLog().isDebugEnabled())
365 {
366 rsvc.getLog().debug(stop.getMessage());
367 }
368 }
369 catch (IOException e)
370 {
371 throw new VelocityException("IO Error rendering template '"+ name + "'", e);
372 }
373 finally
374 {
375 /*
376 * lets make sure that we always clean up the context
377 */
378 ica.popCurrentTemplateName();
379 ica.setCurrentResource( null );
380
381 if (provideScope)
382 {
383 Object obj = ica.get(scopeName);
384 if (obj instanceof Scope)
385 {
386 Scope scope = (Scope)obj;
387 if (scope.getParent() != null)
388 {
389 ica.put(scopeName, scope.getParent());
390 }
391 else if (scope.getReplaced() != null)
392 {
393 ica.put(scopeName, scope.getReplaced());
394 }
395 else
396 {
397 ica.remove(scopeName);
398 }
399 }
400 }
401 }
402 }
403 else
404 {
405 /*
406 * this shouldn't happen either, but just in case.
407 */
408
409 String msg = "Template.merge() failure. The document is null, " +
410 "most likely due to parsing error.";
411
412 throw new RuntimeException(msg);
413
414 }
415 }
416 }