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.parser.ParseException;
38 import org.apache.velocity.runtime.parser.node.SimpleNode;
39 import org.apache.velocity.runtime.resource.Resource;
40 import org.apache.velocity.runtime.resource.ResourceManager;
41
42 /**
43 * This class is used for controlling all template
44 * operations. This class uses a parser created
45 * by JavaCC to create an AST that is subsequently
46 * traversed by a Visitor.
47 *
48 * <pre>
49 * // set up and initialize Velocity before this code block
50 *
51 * Template template = Velocity.getTemplate("test.wm");
52 * Context context = new VelocityContext();
53 *
54 * context.put("foo", "bar");
55 * context.put("customer", new Customer());
56 *
57 * template.merge(context, writer);
58 * </pre>
59 *
60 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
61 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
62 * @version $Id: Template.java 685724 2008-08-13 23:12:12Z nbubna $
63 */
64 public class Template extends Resource
65 {
66 private VelocityException errorCondition = null;
67
68 /** Default constructor */
69 public Template()
70 {
71 super();
72
73 setType(ResourceManager.RESOURCE_TEMPLATE);
74 }
75
76 /**
77 * gets the named resource as a stream, parses and inits
78 *
79 * @return true if successful
80 * @throws ResourceNotFoundException if template not found
81 * from any available source.
82 * @throws ParseErrorException if template cannot be parsed due
83 * to syntax (or other) error.
84 * @throws IOException problem reading input stream
85 */
86 public boolean process()
87 throws ResourceNotFoundException, ParseErrorException, IOException
88 {
89 data = null;
90 InputStream is = null;
91 errorCondition = null;
92
93 /*
94 * first, try to get the stream from the loader
95 */
96 try
97 {
98 is = resourceLoader.getResourceStream(name);
99 }
100 catch( ResourceNotFoundException rnfe )
101 {
102 /*
103 * remember and re-throw
104 */
105
106 errorCondition = rnfe;
107 throw rnfe;
108 }
109
110 /*
111 * if that worked, lets protect in case a loader impl
112 * forgets to throw a proper exception
113 */
114
115 if (is != null)
116 {
117 /*
118 * now parse the template
119 */
120
121 try
122 {
123 BufferedReader br = new BufferedReader( new InputStreamReader( is, encoding ) );
124 data = rsvc.parse( br, name);
125 initDocument();
126 return true;
127 }
128 catch( UnsupportedEncodingException uce )
129 {
130 String msg = "Template.process : Unsupported input encoding : " + encoding
131 + " for template " + name;
132
133 errorCondition = new ParseErrorException( msg );
134 throw errorCondition;
135 }
136 catch ( ParseException pex )
137 {
138 /*
139 * remember the error and convert
140 */
141 errorCondition = new ParseErrorException( pex );
142 throw errorCondition;
143 }
144 catch ( TemplateInitException pex )
145 {
146 errorCondition = new ParseErrorException( pex );
147 throw errorCondition;
148 }
149 /**
150 * pass through runtime exceptions
151 */
152 catch( RuntimeException e )
153 {
154 throw new RuntimeException("Exception thrown processing Template "+getName(), e);
155 }
156 finally
157 {
158 /*
159 * Make sure to close the inputstream when we are done.
160 */
161 is.close();
162 }
163 }
164 else
165 {
166 /*
167 * is == null, therefore we have some kind of file issue
168 */
169
170 errorCondition = new ResourceNotFoundException("Unknown resource error for resource " + name );
171 throw errorCondition;
172 }
173 }
174
175 /**
176 * initializes the document. init() is not longer
177 * dependant upon context, but we need to let the
178 * init() carry the template name down throught for VM
179 * namespace features
180 * @throws TemplateInitException When a problem occurs during the document initialization.
181 */
182 public void initDocument()
183 throws TemplateInitException
184 {
185 /*
186 * send an empty InternalContextAdapter down into the AST to initialize it
187 */
188
189 InternalContextAdapterImpl ica = new InternalContextAdapterImpl( new VelocityContext() );
190
191 try
192 {
193 /*
194 * put the current template name on the stack
195 */
196
197 ica.pushCurrentTemplateName( name );
198 ica.setCurrentResource( this );
199
200 /*
201 * init the AST
202 */
203
204 ((SimpleNode)data).init( ica, rsvc);
205 }
206 finally
207 {
208 /*
209 * in case something blows up...
210 * pull it off for completeness
211 */
212
213 ica.popCurrentTemplateName();
214 ica.setCurrentResource( null );
215 }
216
217 }
218
219 /**
220 * The AST node structure is merged with the
221 * context to produce the final output.
222 *
223 * @param context Conext with data elements accessed by template
224 * @param writer output writer for rendered template
225 * @throws ResourceNotFoundException if template not found
226 * from any available source.
227 * @throws ParseErrorException if template cannot be parsed due
228 * to syntax (or other) error.
229 * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
230 * @throws IOException Might be thrown while rendering.
231 */
232 public void merge( Context context, Writer writer)
233 throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, IOException
234 {
235 merge(context, writer, null);
236 }
237
238
239 /**
240 * The AST node structure is merged with the
241 * context to produce the final output.
242 *
243 * @param context Conext with data elements accessed by template
244 * @param writer output writer for rendered template
245 * @param macroLibraries a list of template files containing macros to be used when merging
246 * @throws ResourceNotFoundException if template not found
247 * from any available source.
248 * @throws ParseErrorException if template cannot be parsed due
249 * to syntax (or other) error.
250 * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
251 * @throws IOException Might be thrown while rendering.
252 * @since 1.6
253 */
254 public void merge( Context context, Writer writer, List macroLibraries)
255 throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, IOException
256 {
257 /*
258 * we shouldn't have to do this, as if there is an error condition,
259 * the application code should never get a reference to the
260 * Template
261 */
262
263 if (errorCondition != null)
264 {
265 throw errorCondition;
266 }
267
268 if( data != null)
269 {
270 /*
271 * create an InternalContextAdapter to carry the user Context down
272 * into the rendering engine. Set the template name and render()
273 */
274
275 InternalContextAdapterImpl ica = new InternalContextAdapterImpl( context );
276
277 /**
278 * Set the macro libraries
279 */
280 ica.setMacroLibraries(macroLibraries);
281
282 if (macroLibraries != null)
283 {
284 for (int i = 0; i < macroLibraries.size(); i++)
285 {
286 /**
287 * Build the macro library
288 */
289 try
290 {
291 rsvc.getTemplate((String) macroLibraries.get(i));
292 }
293 catch (ResourceNotFoundException re)
294 {
295 /*
296 * the macro lib wasn't found. Note it and throw
297 */
298 rsvc.getLog().error("template.merge(): " +
299 "cannot find template " +
300 (String) macroLibraries.get(i));
301 throw re;
302 }
303 catch (ParseErrorException pe)
304 {
305 /*
306 * the macro lib was found, but didn't parse - syntax error
307 * note it and throw
308 */
309 rsvc.getLog().error("template.merge(): " +
310 "syntax error in template " +
311 (String) macroLibraries.get(i) + ".");
312 throw pe;
313 }
314
315 catch (Exception e)
316 {
317 throw new RuntimeException("Template.merge(): parse failed in template " +
318 (String) macroLibraries.get(i) + ".", e);
319 }
320 }
321 }
322
323 try
324 {
325 ica.pushCurrentTemplateName( name );
326 ica.setCurrentResource( this );
327
328 ( (SimpleNode) data ).render( ica, writer);
329 }
330 finally
331 {
332 /*
333 * lets make sure that we always clean up the context
334 */
335 ica.popCurrentTemplateName();
336 ica.setCurrentResource( null );
337 }
338 }
339 else
340 {
341 /*
342 * this shouldn't happen either, but just in case.
343 */
344
345 String msg = "Template.merge() failure. The document is null, " +
346 "most likely due to parsing error.";
347
348 throw new RuntimeException(msg);
349
350 }
351 }
352 }