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