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