• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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=&lt;!-- #include error :
59  *       include.output.errormsg.end= --&gt;
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