• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.apache.velocity.context.Context;
23 import org.apache.velocity.context.InternalContextAdapterImpl;
24 import org.apache.velocity.exception.MethodInvocationException;
25 import org.apache.velocity.exception.ParseErrorException;
26 import org.apache.velocity.exception.ResourceNotFoundException;
27 import org.apache.velocity.exception.TemplateInitException;
28 import org.apache.velocity.exception.VelocityException;
29 import org.apache.velocity.runtime.directive.Scope;
30 import org.apache.velocity.runtime.directive.StopCommand;
31 import org.apache.velocity.runtime.parser.ParseException;
32 import org.apache.velocity.runtime.parser.node.SimpleNode;
33 import org.apache.velocity.runtime.resource.Resource;
34 import org.apache.velocity.runtime.resource.ResourceManager;
35 import org.slf4j.Logger;
36 
37 import java.io.BufferedReader;
38 import java.io.IOException;
39 import java.io.Reader;
40 import java.io.Writer;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.ConcurrentHashMap;
45 
46 /**
47  * This class is used for controlling all template
48  * operations. This class uses a parser created
49  * by JavaCC to create an AST that is subsequently
50  * traversed by a Visitor.
51  *
52  * <pre>
53  * // set up and initialize Velocity before this code block
54  *
55  * Template template = Velocity.getTemplate("test.wm");
56  * Context context = new VelocityContext();
57  *
58  * context.put("foo", "bar");
59  * context.put("customer", new Customer());
60  *
61  * template.merge(context, writer);
62  * </pre>
63  *
64  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
65  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
66  * @version $Id$
67  */
68 public class Template extends Resource implements Cloneable
69 {
70     /*
71      * The name of the variable to use when placing
72      * the scope object into the context.
73      */
74     private String scopeName = "template";
75     private boolean provideScope = false;
76     private Map<String, Object> macros = new ConcurrentHashMap<>(17, 0.7f);
77 
78     private VelocityException errorCondition = null;
79 
80     /** Default constructor */
Template()81     public Template()
82     {
83         super();
84 
85         setType(ResourceManager.RESOURCE_TEMPLATE);
86     }
87 
88     /**
89      * get the map of all macros defined by this template
90      * @return macros map
91      */
getMacros()92     public Map<String, Object> getMacros()
93     {
94         return macros;
95     }
96 
97     /**
98      *  gets the named resource as a stream, parses and inits
99      *
100      * @return true if successful
101      * @throws ResourceNotFoundException if template not found
102      *          from any available source.
103      * @throws ParseErrorException if template cannot be parsed due
104      *          to syntax (or other) error.
105      */
106     @Override
process()107     public boolean process()
108         throws ResourceNotFoundException, ParseErrorException
109     {
110         data = null;
111         Reader reader = null;
112         errorCondition = null;
113 
114         /*
115          *  first, try to get the stream from the loader
116          */
117         try
118         {
119             reader = resourceLoader.getResourceReader(name, getEncoding());
120         }
121         catch( ResourceNotFoundException rnfe )
122         {
123             /*
124              *  remember and re-throw
125              */
126 
127             errorCondition = rnfe;
128             throw rnfe;
129         }
130 
131         /*
132          *  if that worked, lets protect in case a loader impl
133          *  forgets to throw a proper exception
134          */
135 
136         if (reader != null)
137         {
138             /*
139              *  now parse the template
140              */
141 
142             try
143             {
144                 BufferedReader br = new BufferedReader( reader );
145                 data = rsvc.parse( br, this);
146                 initDocument();
147                 return true;
148             }
149             catch ( ParseException pex )
150             {
151                 /*
152                  *  remember the error and convert
153                  */
154                 errorCondition =  new ParseErrorException(pex, name);
155                 throw errorCondition;
156             }
157             catch ( TemplateInitException pex )
158             {
159                 errorCondition = new ParseErrorException( pex, name);
160                 throw errorCondition;
161             }
162             /*
163              * pass through runtime exceptions
164              */
165             catch( RuntimeException e )
166             {
167                 errorCondition = new VelocityException("Exception thrown processing Template "
168                     +getName(), e, rsvc.getLogContext().getStackTrace());
169                 throw errorCondition;
170             }
171             finally
172             {
173                 /*
174                  *  Make sure to close the inputstream when we are done.
175                  */
176                 try
177                 {
178                     reader.close();
179                 }
180                 catch(IOException e)
181                 {
182                     // If we are already throwing an exception then we want the original
183                     // exception to be continued to be thrown, otherwise, throw a new Exception.
184                     if (errorCondition == null)
185                     {
186                          throw new VelocityException(e, rsvc.getLogContext().getStackTrace());
187                     }
188                 }
189             }
190         }
191         else
192         {
193             /*
194              *  is == null, therefore we have some kind of file issue
195              */
196             errorCondition = new ResourceNotFoundException("Unknown resource error for resource " + name, null, rsvc.getLogContext().getStackTrace() );
197             throw errorCondition;
198         }
199     }
200 
201     /**
202      *  initializes the document.  init() is not longer
203      *  dependant upon context, but we need to let the
204      *  init() carry the template name down through for VM
205      *  namespace features
206      * @throws TemplateInitException When a problem occurs during the document initialization.
207      */
initDocument()208     public void initDocument()
209     throws TemplateInitException
210     {
211         /*
212          *  send an empty InternalContextAdapter down into the AST to initialize it
213          */
214 
215         InternalContextAdapterImpl ica = new InternalContextAdapterImpl(  new VelocityContext() );
216 
217         try
218         {
219             /*
220              *  put the current template name on the stack
221              */
222 
223             ica.pushCurrentTemplateName( name );
224             ica.setCurrentResource( this );
225 
226             /*
227              *  init the AST
228              */
229 
230             ((SimpleNode)data).init( ica, rsvc);
231 
232             provideScope = rsvc.isScopeControlEnabled(scopeName);
233         }
234         finally
235         {
236             /*
237              *  in case something blows up...
238              *  pull it off for completeness
239              */
240 
241             ica.popCurrentTemplateName();
242             ica.setCurrentResource( null );
243         }
244 
245     }
246 
247     /**
248      * The AST node structure is merged with the
249      * context to produce the final output.
250      *
251      *  @param context Context with data elements accessed by template
252      *  @param writer output writer for rendered template
253      *  @throws ResourceNotFoundException if template not found
254      *          from any available source.
255      *  @throws ParseErrorException if template cannot be parsed due
256      *          to syntax (or other) error.
257      *  @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
258      */
merge( Context context, Writer writer)259     public void merge( Context context, Writer writer)
260         throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
261     {
262         merge(context, writer, null);
263     }
264 
265 
266     /**
267      * The AST node structure is merged with the
268      * context to produce the final output.
269      *
270      *  @param context Context with data elements accessed by template
271      *  @param writer output writer for rendered template
272      *  @param macroLibraries a list of template files containing macros to be used when merging
273      *  @throws ResourceNotFoundException if template not found
274      *          from any available source.
275      *  @throws ParseErrorException if template cannot be parsed due
276      *          to syntax (or other) error.
277      *  @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
278      *  @since 1.6
279      */
merge( Context context, Writer writer, List<String> macroLibraries)280     public void merge( Context context, Writer writer, List<String> macroLibraries)
281         throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
282     {
283         try
284         {
285             /*
286              *  we shouldn't have to do this, as if there is an error condition,
287              *  the application code should never get a reference to the
288              *  Template
289              */
290 
291             if (errorCondition != null)
292             {
293                 throw errorCondition;
294             }
295 
296             if (data != null)
297             {
298                 /*
299                  *  create an InternalContextAdapter to carry the user Context down
300                  *  into the rendering engine.  Set the template name and render()
301                  */
302 
303                 InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
304 
305                 /*
306                  * Set the macro libraries
307                  */
308                 List<Template> libTemplates = new ArrayList<>();
309                 ica.setMacroLibraries(libTemplates);
310 
311                 if (macroLibraries != null)
312                 {
313                     for (String macroLibrary : macroLibraries)
314                     {
315                         /*
316                          * Build the macro library
317                          */
318                         try
319                         {
320                             Template t = rsvc.getTemplate(macroLibrary);
321                             libTemplates.add(t);
322                         }
323                         catch (ResourceNotFoundException re)
324                         {
325                             /*
326                              * the macro lib wasn't found.  Note it and throw
327                              */
328                             log.error("cannot find template {}", macroLibrary);
329                             throw re;
330                         }
331                         catch (ParseErrorException pe)
332                         {
333                             /*
334                              * the macro lib was found, but didn't parse - syntax error
335                              *  note it and throw
336                              */
337                             rsvc.getLog("parser").error("syntax error in template {}: {}",
338                                 macroLibrary, pe.getMessage(), pe);
339                             throw pe;
340                         }
341                         catch (Exception e)
342                         {
343                             throw new RuntimeException("parse failed in template  " +
344                                 macroLibrary + ".", e);
345                         }
346                     }
347                 }
348 
349                 if (provideScope)
350                 {
351                     ica.put(scopeName, new Scope(this, ica.get(scopeName)));
352                 }
353                 try
354                 {
355                     ica.pushCurrentTemplateName(name);
356                     ica.setCurrentResource(this);
357 
358                     ((SimpleNode) data).render(ica, writer);
359                 }
360                 catch (StopCommand stop)
361                 {
362                     if (!stop.isFor(this))
363                     {
364                         throw stop;
365                     }
366                     else
367                     {
368                         Logger renderingLog = rsvc.getLog("rendering");
369                         renderingLog.debug(stop.getMessage());
370                     }
371                 }
372                 catch (IOException e)
373                 {
374                     throw new VelocityException("IO Error rendering template '" + name + "'", e, rsvc.getLogContext().getStackTrace());
375                 }
376                 finally
377                 {
378                     /*
379                      *  lets make sure that we always clean up the context
380                      */
381                     ica.popCurrentTemplateName();
382                     ica.setCurrentResource(null);
383 
384                     if (provideScope)
385                     {
386                         Object obj = ica.get(scopeName);
387                         if (obj instanceof Scope)
388                         {
389                             Scope scope = (Scope) obj;
390                             if (scope.getParent() != null)
391                             {
392                                 ica.put(scopeName, scope.getParent());
393                             }
394                             else if (scope.getReplaced() != null)
395                             {
396                                 ica.put(scopeName, scope.getReplaced());
397                             }
398                             else
399                             {
400                                 ica.remove(scopeName);
401                             }
402                         }
403                     }
404                 }
405             }
406             else
407             {
408                 /*
409                  * this shouldn't happen either, but just in case.
410                  */
411 
412                 String msg = "Template merging failed. The document is null, " +
413                     "most likely due to a parsing error.";
414 
415                 throw new RuntimeException(msg);
416 
417             }
418         }
419         catch (VelocityException ve)
420         {
421             /* it's a good place to display the VTL stack trace if we have one */
422             String[] vtlStacktrace = ve.getVtlStackTrace();
423             if (vtlStacktrace != null)
424             {
425                 Logger renderingLog = rsvc.getLog("rendering");
426                 renderingLog.error(ve.getMessage());
427                 renderingLog.error("VTL stacktrace:");
428                 for (String level : vtlStacktrace)
429                 {
430                     renderingLog.error(level);
431                 }
432             }
433             throw ve;
434         }
435     }
436 
437     @Override
deepCloneData()438     protected void deepCloneData() throws CloneNotSupportedException {
439         setData(((SimpleNode)data).clone(this));
440     }
441 }
442