View Javadoc

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 }