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=<!-- #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: 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 }