1 package org.apache.velocity.runtime.directive; 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.Template; 23 import org.apache.velocity.app.event.EventHandlerUtil; 24 import org.apache.velocity.context.InternalContextAdapter; 25 import org.apache.velocity.exception.MethodInvocationException; 26 import org.apache.velocity.exception.ParseErrorException; 27 import org.apache.velocity.exception.ResourceNotFoundException; 28 import org.apache.velocity.exception.TemplateInitException; 29 import org.apache.velocity.exception.VelocityException; 30 import org.apache.velocity.runtime.RuntimeConstants; 31 import org.apache.velocity.runtime.RuntimeServices; 32 import org.apache.velocity.runtime.parser.ParseException; 33 import org.apache.velocity.runtime.parser.Token; 34 import org.apache.velocity.runtime.parser.node.Node; 35 import org.apache.velocity.runtime.parser.node.ParserTreeConstants; 36 import org.apache.velocity.runtime.parser.node.SimpleNode; 37 import org.apache.velocity.util.StringUtils; 38 39 import java.io.IOException; 40 import java.io.Writer; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Pluggable directive that handles the <code>#parse()</code> 46 * statement in VTL. 47 * 48 * <pre> 49 * Notes: 50 * ----- 51 * 1) The parsed source material can only come from somewhere in 52 * the TemplateRoot tree for security reasons. There is no way 53 * around this. If you want to include content from elsewhere on 54 * your disk, use a link from somewhere under Template Root to that 55 * content. 56 * 57 * 2) There is a limited parse depth. It is set as a property 58 * "directive.parse.max_depth = 10" by default. This 10 deep 59 * limit is a safety feature to prevent infinite loops. 60 * </pre> 61 * 62 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 63 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 64 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> 65 * @version $Id$ 66 */ 67 public class Parse extends InputBase 68 { 69 private int maxDepth; 70 71 /** 72 * Indicates if we are running in strict reference mode. 73 */ 74 public boolean strictRef = false; 75 76 /** 77 * Return name of this directive. 78 * @return The name of this directive. 79 */ 80 @Override getName()81 public String getName() 82 { 83 return "parse"; 84 } 85 86 /** 87 * Overrides the default to use "template", so that all templates 88 * can use the same scope reference, whether rendered via #parse 89 * or direct merge. 90 */ 91 @Override getScopeName()92 public String getScopeName() 93 { 94 return "template"; 95 } 96 97 /** 98 * Return type of this directive. 99 * @return The type of this directive. 100 */ 101 @Override getType()102 public int getType() 103 { 104 return LINE; 105 } 106 107 /** 108 * Init's the #parse directive. 109 * @param rs 110 * @param context 111 * @param node 112 * @throws TemplateInitException 113 */ 114 @Override init(RuntimeServices rs, InternalContextAdapter context, Node node)115 public void init(RuntimeServices rs, InternalContextAdapter context, Node node) 116 throws TemplateInitException 117 { 118 super.init(rs, context, node); 119 120 this.maxDepth = rsvc.getInt(RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 10); 121 122 strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); 123 } 124 125 /** 126 * iterates through the argument list and renders every 127 * argument that is appropriate. Any non appropriate 128 * arguments are logged, but render() continues. 129 * @param context 130 * @param writer 131 * @param node 132 * @return True if the directive rendered successfully. 133 * @throws IOException 134 * @throws ResourceNotFoundException 135 * @throws ParseErrorException 136 * @throws MethodInvocationException 137 */ 138 @Override render(InternalContextAdapter context, Writer writer, Node node)139 public boolean render(InternalContextAdapter context, 140 Writer writer, Node node) 141 throws IOException, ResourceNotFoundException, ParseErrorException, 142 MethodInvocationException 143 { 144 /* 145 * did we get an argument? 146 */ 147 if ( node.jjtGetNumChildren() == 0 ) 148 { 149 throw new VelocityException("#" + getName() + "(): argument missing at " + 150 StringUtils.formatFileString(this), null, rsvc.getLogContext().getStackTrace()); 151 } 152 153 /* 154 * does it have a value? If you have a null reference, then no. 155 */ 156 Object value = node.jjtGetChild(0).value( context ); 157 if (value == null) 158 { 159 log.debug("#" + getName() + "(): null argument at {}", StringUtils.formatFileString(this)); 160 } 161 162 /* 163 * get the path 164 */ 165 String sourcearg = value == null ? null : value.toString(); 166 167 /* 168 * check to see if the argument will be changed by the event cartridge 169 */ 170 String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName()); 171 172 /* 173 * if strict mode and arg was not fixed by event handler, then complain 174 */ 175 if (strictRef && value == null && arg == null) 176 { 177 throw new VelocityException("The argument to #" + getName() + " returned null at " 178 + StringUtils.formatFileString(this), null, rsvc.getLogContext().getStackTrace()); 179 } 180 181 /* 182 * a null return value from the event cartridge indicates we should not 183 * input a resource. 184 */ 185 if (arg == null) 186 { 187 // abort early, but still consider it a successful rendering 188 return true; 189 } 190 191 192 if (maxDepth > 0) 193 { 194 /* 195 * see if we have exceeded the configured depth. 196 */ 197 String[] templateStack = context.getTemplateNameStack(); 198 if (templateStack.length >= maxDepth) 199 { 200 StringBuilder path = new StringBuilder(); 201 for (String aTemplateStack : templateStack) 202 { 203 path.append(" > ").append(aTemplateStack); 204 } 205 log.error("Max recursion depth reached ({}). File stack: {}", 206 templateStack.length, path); 207 208 return false; 209 } 210 } 211 212 /* 213 * now use the Runtime resource loader to get the template 214 */ 215 216 Template t = null; 217 218 try 219 { 220 t = getTemplate(arg, getInputEncoding(context)); 221 } 222 catch ( ResourceNotFoundException rnfe ) 223 { 224 /* 225 * the arg wasn't found. Note it and throw 226 */ 227 log.error("#" + getName() + "(): cannot find template '{}', called at {}", 228 arg, StringUtils.formatFileString(this)); 229 throw rnfe; 230 } 231 catch ( ParseErrorException pee ) 232 { 233 /* 234 * the arg was found, but didn't parse - syntax error 235 * note it and throw 236 */ 237 log.error("#" + getName() + "(): syntax error in #" + getName() + "()-ed template '{}', called at {}", 238 arg, StringUtils.formatFileString(this)); 239 throw pee; 240 } 241 /* 242 * pass through application level runtime exceptions 243 */ 244 catch( RuntimeException e ) 245 { 246 log.error("Exception rendering #" + getName() + "({}) at {}", 247 arg, StringUtils.formatFileString(this)); 248 throw e; 249 } 250 catch ( Exception e ) 251 { 252 String msg = "Exception rendering #" + getName() + "(" + arg + ") at " + 253 StringUtils.formatFileString(this); 254 log.error(msg, e); 255 throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); 256 } 257 258 /* 259 * Add the template name to the macro libraries list 260 */ 261 List<Template> macroLibraries = context.getMacroLibraries(); 262 263 /* 264 * if macroLibraries are not set create a new one 265 */ 266 if (macroLibraries == null) 267 { 268 macroLibraries = new ArrayList<>(); 269 } 270 271 context.setMacroLibraries(macroLibraries); 272 273 /* instead of adding the name of the template, add the Template reference */ 274 macroLibraries.add(t); 275 276 /* 277 * and render it 278 */ 279 try 280 { 281 preRender(context); 282 context.pushCurrentTemplateName(arg); 283 284 ((SimpleNode) t.getData()).render(context, writer); 285 } 286 catch( StopCommand stop ) 287 { 288 if (!stop.isFor(this)) 289 { 290 throw stop; 291 } 292 } 293 /* 294 * pass through application level runtime exceptions 295 */ 296 catch( RuntimeException e ) 297 { 298 /* 299 * Log #parse errors so the user can track which file called which. 300 */ 301 log.error("Exception rendering #" + getName() + "({}) at {}", 302 arg, StringUtils.formatFileString(this)); 303 throw e; 304 } 305 catch ( Exception e ) 306 { 307 String msg = "Exception rendering #" + getName() + "(" + arg + ") at " + 308 StringUtils.formatFileString(this); 309 log.error(msg, e); 310 throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); 311 } 312 finally 313 { 314 context.popCurrentTemplateName(); 315 postRender(context); 316 } 317 318 /* 319 * note - a blocked input is still a successful operation as this is 320 * expected behavior. 321 */ 322 323 return true; 324 } 325 326 /** 327 * Called by the parser to validate the argument types 328 */ 329 @Override checkArgs(ArrayList<Integer> argtypes, Token t, String templateName)330 public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) 331 throws ParseException 332 { 333 if (argtypes.size() != 1) 334 { 335 throw new MacroParseException("The #" + getName() + " directive requires one argument", 336 templateName, t); 337 } 338 339 if (argtypes.get(0) == ParserTreeConstants.JJTWORD) 340 { 341 throw new MacroParseException("The argument to #" + getName() + " is of the wrong type", 342 templateName, t); 343 } 344 } 345 346 /** 347 * Find the template to render in the appropriate encoding 348 * @param path template path 349 * @param encoding template encoding 350 * @return found template 351 * @throws ResourceNotFoundException if template was not found 352 * @since 2.4 353 */ getTemplate(String path, String encoding)354 protected Template getTemplate(String path, String encoding) throws ResourceNotFoundException 355 { 356 return rsvc.getTemplate(path, encoding); 357 } 358 } 359