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