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