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.app.event.EventHandlerUtil; 23 import org.apache.velocity.context.InternalContextAdapter; 24 import org.apache.velocity.exception.MethodInvocationException; 25 import org.apache.velocity.exception.ResourceNotFoundException; 26 import org.apache.velocity.exception.TemplateInitException; 27 import org.apache.velocity.exception.VelocityException; 28 import org.apache.velocity.runtime.RuntimeConstants; 29 import org.apache.velocity.runtime.RuntimeServices; 30 import org.apache.velocity.runtime.parser.node.Node; 31 import org.apache.velocity.runtime.parser.node.ParserTreeConstants; 32 import org.apache.velocity.runtime.resource.Resource; 33 import org.apache.velocity.util.StringUtils; 34 35 import java.io.IOException; 36 import java.io.Writer; 37 38 /** 39 * <p>Pluggable directive that handles the #include() statement in VTL. 40 * This #include() can take multiple arguments of either 41 * StringLiteral or Reference.</p> 42 * 43 * <p>Notes:</p> 44 * <ol> 45 * <li>For security reasons, the included source material can only come 46 * from somewhere within the template root tree. If you want to include 47 * content from elsewhere on your disk, add extra template roots, or use 48 * a link from somwehere under template root to that content.</li> 49 * 50 * <li>By default, there is no output to the render stream in the event of 51 * a problem. You can override this behavior with two property values : 52 * include.output.errormsg.start 53 * include.output.errormsg.end 54 * If both are defined in velocity.properties, they will be used to 55 * in the render output to bracket the arg string that caused the 56 * problem. 57 * Ex. : if you are working in html then 58 * include.output.errormsg.start=<!-- #include error : 59 * include.output.errormsg.end= --> 60 * might be an excellent way to start...</li> 61 * 62 * <li>As noted above, #include() can take multiple arguments. 63 * Ex : #include('foo.vm' 'bar.vm' $foo) 64 * will include all three if valid to output without any 65 * special separator.</li> 66 * </ol> 67 * 68 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 69 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 70 * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a> 71 * @version $Id$ 72 */ 73 public class Include extends InputBase 74 { 75 private String outputMsgStart = ""; 76 private String outputMsgEnd = ""; 77 78 /** 79 * Return name of this directive. 80 * @return The name of this directive. 81 */ 82 @Override getName()83 public String getName() 84 { 85 return "include"; 86 } 87 88 /** 89 * Return type of this directive. 90 * @return The type of this directive. 91 */ 92 @Override getType()93 public int getType() 94 { 95 return LINE; 96 } 97 98 /** 99 * Since there is no processing of content, 100 * there is never a need for an internal scope. 101 */ 102 @Override isScopeProvided()103 public boolean isScopeProvided() 104 { 105 return false; 106 } 107 108 /** 109 * simple init - init the tree and get the elementKey from 110 * the AST 111 * @param rs 112 * @param context 113 * @param node 114 * @throws TemplateInitException 115 */ 116 @Override init(RuntimeServices rs, InternalContextAdapter context, Node node)117 public void init(RuntimeServices rs, InternalContextAdapter context, 118 Node node) 119 throws TemplateInitException 120 { 121 super.init( rs, context, node ); 122 123 /* 124 * get the msg, and add the space so we don't have to 125 * do it each time 126 */ 127 outputMsgStart = rsvc.getString(RuntimeConstants.ERRORMSG_START); 128 outputMsgStart = outputMsgStart + " "; 129 130 outputMsgEnd = rsvc.getString(RuntimeConstants.ERRORMSG_END ); 131 outputMsgEnd = " " + outputMsgEnd; 132 } 133 134 /** 135 * iterates through the argument list and renders every 136 * argument that is appropriate. Any non appropriate 137 * arguments are logged, but render() continues. 138 * @param context 139 * @param writer 140 * @param node 141 * @return True if the directive rendered successfully. 142 * @throws IOException 143 * @throws MethodInvocationException 144 * @throws ResourceNotFoundException 145 */ 146 @Override render(InternalContextAdapter context, Writer writer, Node node)147 public boolean render(InternalContextAdapter context, 148 Writer writer, Node node) 149 throws IOException, MethodInvocationException, 150 ResourceNotFoundException 151 { 152 /* 153 * get our arguments and check them 154 */ 155 156 int argCount = node.jjtGetNumChildren(); 157 158 for( int i = 0; i < argCount; i++) 159 { 160 /* 161 * we only handle StringLiterals and References right now 162 */ 163 164 Node n = node.jjtGetChild(i); 165 166 if ( n.getType() == ParserTreeConstants.JJTSTRINGLITERAL || 167 n.getType() == ParserTreeConstants.JJTREFERENCE ) 168 { 169 if (!renderOutput( n, context, writer )) 170 outputErrorToStream( writer, "error with arg " + i 171 + " please see log."); 172 } 173 else 174 { 175 String msg = "invalid #include() argument '" 176 + n.toString() + "' at " + StringUtils.formatFileString(this); 177 log.error(msg); 178 outputErrorToStream( writer, "error with arg " + i 179 + " please see log."); 180 throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); 181 } 182 } 183 184 return true; 185 } 186 187 /** 188 * does the actual rendering of the included file 189 * 190 * @param node AST argument of type StringLiteral or Reference 191 * @param context valid context so we can render References 192 * @param writer output Writer 193 * @return boolean success or failure. failures are logged 194 * @exception IOException 195 * @exception MethodInvocationException 196 * @exception ResourceNotFoundException 197 */ renderOutput( Node node, InternalContextAdapter context, Writer writer )198 private boolean renderOutput( Node node, InternalContextAdapter context, 199 Writer writer ) 200 throws IOException, MethodInvocationException, 201 ResourceNotFoundException 202 { 203 if ( node == null ) 204 { 205 log.error("#include() null argument"); 206 return false; 207 } 208 209 /* 210 * does it have a value? If you have a null reference, then no. 211 */ 212 Object value = node.value( context ); 213 if ( value == null) 214 { 215 log.error("#include() null argument"); 216 return false; 217 } 218 219 /* 220 * get the path 221 */ 222 String sourcearg = value.toString(); 223 224 /* 225 * check to see if the argument will be changed by the event handler 226 */ 227 228 String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName() ); 229 230 /* 231 * a null return value from the event cartridge indicates we should not 232 * input a resource. 233 */ 234 boolean blockinput = false; 235 if (arg == null) 236 blockinput = true; 237 238 Resource resource = null; 239 240 try 241 { 242 if (!blockinput) 243 resource = getResource(arg, getInputEncoding(context)); 244 } 245 catch ( ResourceNotFoundException rnfe ) 246 { 247 /* 248 * the arg wasn't found. Note it and throw 249 */ 250 log.error("#" + getName() + "(): cannot find resource '{}', called at {}", 251 arg, StringUtils.formatFileString(this)); 252 throw rnfe; 253 } 254 255 /* 256 * pass through application level runtime exceptions 257 */ 258 catch( RuntimeException e ) 259 { 260 log.error("#" + getName() + "(): arg = '{}', called at {}", 261 arg, StringUtils.formatFileString(this)); 262 throw e; 263 } 264 catch (Exception e) 265 { 266 String msg = "#" + getName() + "(): arg = '" + arg + 267 "', called at " + StringUtils.formatFileString(this); 268 log.error(msg, e); 269 throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); 270 } 271 272 273 /* 274 * note - a blocked input is still a successful operation as this is 275 * expected behavior. 276 */ 277 278 if ( blockinput ) 279 return true; 280 281 else if ( resource == null ) 282 return false; 283 284 writer.write((String)resource.getData()); 285 return true; 286 } 287 288 /** 289 * Puts a message to the render output stream if ERRORMSG_START / END 290 * are valid property strings. Mainly used for end-user template 291 * debugging. 292 * @param writer 293 * @param msg 294 * @throws IOException 295 * @deprecated if/how errors are displayed is not the concern of the engine, which should throw in all cases 296 */ outputErrorToStream( Writer writer, String msg )297 private void outputErrorToStream( Writer writer, String msg ) 298 throws IOException 299 { 300 if ( outputMsgStart != null && outputMsgEnd != null) 301 { 302 writer.write(outputMsgStart); 303 writer.write(msg); 304 writer.write(outputMsgEnd); 305 } 306 } 307 308 /** 309 * Find the resource to include 310 * @param path resource path 311 * @param encoding resource encoding 312 * @return found resource 313 * @throws ResourceNotFoundException if resource was not found 314 */ getResource(String path, String encoding)315 protected Resource getResource(String path, String encoding) throws ResourceNotFoundException 316 { 317 return rsvc.getContent(path, encoding); 318 } 319 } 320