View Javadoc

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 java.io.IOException;
23  import java.io.Writer;
24  
25  import org.apache.velocity.app.event.EventHandlerUtil;
26  import org.apache.velocity.context.InternalContextAdapter;
27  import org.apache.velocity.exception.MethodInvocationException;
28  import org.apache.velocity.exception.ResourceNotFoundException;
29  import org.apache.velocity.exception.TemplateInitException;
30  import org.apache.velocity.exception.VelocityException;
31  import org.apache.velocity.runtime.RuntimeConstants;
32  import org.apache.velocity.runtime.RuntimeServices;
33  import org.apache.velocity.runtime.log.Log;
34  import org.apache.velocity.runtime.parser.ParserTreeConstants;
35  import org.apache.velocity.runtime.parser.node.Node;
36  import org.apache.velocity.runtime.resource.Resource;
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 somwhere 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: Include.java 746438 2009-02-21 05:41:24Z nbubna $
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      public String getName()
83      {
84          return "include";
85      }
86  
87      /**
88       * Return type of this directive.
89       * @return The type of this directive.
90       */
91      public int getType()
92      {
93          return LINE;
94      }
95  
96      /**
97       * Since there is no processing of content,
98       * there is never a need for an internal scope.
99       */
100     public boolean isScopeProvided()
101     {
102         return false;
103     }
104 
105     /**
106      *  simple init - init the tree and get the elementKey from
107      *  the AST
108      * @param rs
109      * @param context
110      * @param node
111      * @throws TemplateInitException
112      */
113     public void init(RuntimeServices rs, InternalContextAdapter context,
114                      Node node)
115         throws TemplateInitException
116     {
117         super.init( rs, context, node );
118 
119         /*
120          *  get the msg, and add the space so we don't have to
121          *  do it each time
122          */
123         outputMsgStart = rsvc.getString(RuntimeConstants.ERRORMSG_START);
124         outputMsgStart = outputMsgStart + " ";
125 
126         outputMsgEnd = rsvc.getString(RuntimeConstants.ERRORMSG_END );
127         outputMsgEnd = " " + outputMsgEnd;
128     }
129 
130     /**
131      *  iterates through the argument list and renders every
132      *  argument that is appropriate.  Any non appropriate
133      *  arguments are logged, but render() continues.
134      * @param context
135      * @param writer
136      * @param node
137      * @return True if the directive rendered successfully.
138      * @throws IOException
139      * @throws MethodInvocationException
140      * @throws ResourceNotFoundException
141      */
142     public boolean render(InternalContextAdapter context,
143                            Writer writer, Node node)
144         throws IOException, MethodInvocationException,
145                ResourceNotFoundException
146     {
147         /*
148          *  get our arguments and check them
149          */
150 
151         int argCount = node.jjtGetNumChildren();
152 
153         for( int i = 0; i < argCount; i++)
154         {
155             /*
156              *  we only handle StringLiterals and References right now
157              */
158 
159             Node n = node.jjtGetChild(i);
160 
161             if ( n.getType() ==  ParserTreeConstants.JJTSTRINGLITERAL ||
162                  n.getType() ==  ParserTreeConstants.JJTREFERENCE )
163             {
164                 if (!renderOutput( n, context, writer ))
165                     outputErrorToStream( writer, "error with arg " + i
166                         + " please see log.");
167             }
168             else
169             {
170                 String msg = "invalid #include() argument '" 
171                   + n.toString() + "' at " + Log.formatFileString(this);
172                 rsvc.getLog().error(msg);
173                 outputErrorToStream( writer, "error with arg " + i
174                     + " please see log.");
175                 throw new VelocityException(msg);
176             }
177         }
178 
179         return true;
180     }
181 
182     /**
183      *  does the actual rendering of the included file
184      *
185      *  @param node AST argument of type StringLiteral or Reference
186      *  @param context valid context so we can render References
187      *  @param writer output Writer
188      *  @return boolean success or failure.  failures are logged
189      *  @exception IOException
190      *  @exception MethodInvocationException
191      *  @exception ResourceNotFoundException
192      */
193     private boolean renderOutput( Node node, InternalContextAdapter context,
194                                   Writer writer )
195         throws IOException, MethodInvocationException,
196                ResourceNotFoundException
197     {
198         if ( node == null )
199         {
200             rsvc.getLog().error("#include() null argument");
201             return false;
202         }
203 
204         /*
205          *  does it have a value?  If you have a null reference, then no.
206          */
207         Object value = node.value( context );
208         if ( value == null)
209         {
210             rsvc.getLog().error("#include() null argument");
211             return false;
212         }
213 
214         /*
215          *  get the path
216          */
217         String sourcearg = value.toString();
218 
219         /*
220          *  check to see if the argument will be changed by the event handler
221          */
222 
223         String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName() );
224 
225         /*
226          *   a null return value from the event cartridge indicates we should not
227          *   input a resource.
228          */
229         boolean blockinput = false;
230         if (arg == null)
231             blockinput = true;
232 
233         Resource resource = null;
234 
235         try
236         {
237             if (!blockinput)
238                 resource = rsvc.getContent(arg, getInputEncoding(context));
239         }
240         catch ( ResourceNotFoundException rnfe )
241         {
242             /*
243              * the arg wasn't found.  Note it and throw
244              */
245             rsvc.getLog().error("#include(): cannot find resource '" + arg +
246                                 "', called at " + Log.formatFileString(this));
247             throw rnfe;
248         }
249 
250         /**
251          * pass through application level runtime exceptions
252          */
253         catch( RuntimeException e )
254         {
255             rsvc.getLog().error("#include(): arg = '" + arg +
256                                 "', called at " + Log.formatFileString(this));
257             throw e;
258         }
259         catch (Exception e)
260         {
261             String msg = "#include(): arg = '" + arg +
262                         "', called at " + Log.formatFileString(this);
263             rsvc.getLog().error(msg, e);
264             throw new VelocityException(msg, e);
265         }
266 
267 
268         /*
269          *    note - a blocked input is still a successful operation as this is
270          *    expected behavior.
271          */
272 
273         if ( blockinput )
274             return true;
275 
276         else if ( resource == null )
277             return false;
278 
279         writer.write((String)resource.getData());
280         return true;
281     }
282 
283     /**
284      *  Puts a message to the render output stream if ERRORMSG_START / END
285      *  are valid property strings.  Mainly used for end-user template
286      *  debugging.
287      *  @param writer
288      *  @param msg
289      *  @throws IOException
290      */
291     private void outputErrorToStream( Writer writer, String msg )
292         throws IOException
293     {
294         if ( outputMsgStart != null  && outputMsgEnd != null)
295         {
296             writer.write(outputMsgStart);
297             writer.write(msg);
298             writer.write(outputMsgEnd);
299         }
300     }
301 }