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=<!-- #include error :
58 * include.output.errormsg.end= -->
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 }