001: package org.apache.velocity.runtime.directive;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.IOException;
023: import java.io.Writer;
024:
025: import org.apache.velocity.app.event.EventHandlerUtil;
026: import org.apache.velocity.context.InternalContextAdapter;
027: import org.apache.velocity.exception.MethodInvocationException;
028: import org.apache.velocity.exception.ResourceNotFoundException;
029: import org.apache.velocity.exception.TemplateInitException;
030: import org.apache.velocity.runtime.RuntimeConstants;
031: import org.apache.velocity.runtime.RuntimeServices;
032: import org.apache.velocity.runtime.parser.ParserTreeConstants;
033: import org.apache.velocity.runtime.parser.node.Node;
034: import org.apache.velocity.runtime.resource.Resource;
035:
036: /**
037: * <p>Pluggable directive that handles the #include() statement in VTL.
038: * This #include() can take multiple arguments of either
039: * StringLiteral or Reference.</p>
040: *
041: * <p>Notes:</p>
042: * <ol>
043: * <li>For security reasons, the included source material can only come
044: * from somewhere within the template root tree. If you want to include
045: * content from elsewhere on your disk, add extra template roots, or use
046: * a link from somwhere under template root to that content.</li>
047: *
048: * <li>By default, there is no output to the render stream in the event of
049: * a problem. You can override this behavior with two property values :
050: * include.output.errormsg.start
051: * include.output.errormsg.end
052: * If both are defined in velocity.properties, they will be used to
053: * in the render output to bracket the arg string that caused the
054: * problem.
055: * Ex. : if you are working in html then
056: * include.output.errormsg.start=<!-- #include error :
057: * include.output.errormsg.end= -->
058: * might be an excellent way to start...</li>
059: *
060: * <li>As noted above, #include() can take multiple arguments.
061: * Ex : #include('foo.vm' 'bar.vm' $foo)
062: * will include all three if valid to output without any
063: * special separator.</li>
064: * </ol>
065: *
066: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
067: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
068: * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a>
069: * @version $Id: Include.java 471381 2006-11-05 08:56:58Z wglass $
070: */
071: public class Include extends InputBase {
072: private String outputMsgStart = "";
073: private String outputMsgEnd = "";
074:
075: /**
076: * Return name of this directive.
077: * @return The name of this directive.
078: */
079: public String getName() {
080: return "include";
081: }
082:
083: /**
084: * Return type of this directive.
085: * @return The type of this directive.
086: */
087: public int getType() {
088: return LINE;
089: }
090:
091: /**
092: * simple init - init the tree and get the elementKey from
093: * the AST
094: * @param rs
095: * @param context
096: * @param node
097: * @throws TemplateInitException
098: */
099: public void init(RuntimeServices rs,
100: InternalContextAdapter context, Node node)
101: throws TemplateInitException {
102: super .init(rs, context, node);
103:
104: /*
105: * get the msg, and add the space so we don't have to
106: * do it each time
107: */
108: outputMsgStart = rsvc
109: .getString(RuntimeConstants.ERRORMSG_START);
110: outputMsgStart = outputMsgStart + " ";
111:
112: outputMsgEnd = rsvc.getString(RuntimeConstants.ERRORMSG_END);
113: outputMsgEnd = " " + outputMsgEnd;
114: }
115:
116: /**
117: * iterates through the argument list and renders every
118: * argument that is appropriate. Any non appropriate
119: * arguments are logged, but render() continues.
120: * @param context
121: * @param writer
122: * @param node
123: * @return True if the directive rendered successfully.
124: * @throws IOException
125: * @throws MethodInvocationException
126: * @throws ResourceNotFoundException
127: */
128: public boolean render(InternalContextAdapter context,
129: Writer writer, Node node) throws IOException,
130: MethodInvocationException, ResourceNotFoundException {
131: /*
132: * get our arguments and check them
133: */
134:
135: int argCount = node.jjtGetNumChildren();
136:
137: for (int i = 0; i < argCount; i++) {
138: /*
139: * we only handle StringLiterals and References right now
140: */
141:
142: Node n = node.jjtGetChild(i);
143:
144: if (n.getType() == ParserTreeConstants.JJTSTRINGLITERAL
145: || n.getType() == ParserTreeConstants.JJTREFERENCE) {
146: if (!renderOutput(n, context, writer))
147: outputErrorToStream(writer, "error with arg " + i
148: + " please see log.");
149: } else {
150: rsvc.getLog().error(
151: "#include() invalid argument type: "
152: + n.toString());
153: outputErrorToStream(writer, "error with arg " + i
154: + " please see log.");
155: }
156: }
157:
158: return true;
159: }
160:
161: /**
162: * does the actual rendering of the included file
163: *
164: * @param node AST argument of type StringLiteral or Reference
165: * @param context valid context so we can render References
166: * @param writer output Writer
167: * @return boolean success or failure. failures are logged
168: * @exception IOException
169: * @exception MethodInvocationException
170: * @exception ResourceNotFoundException
171: */
172: private boolean renderOutput(Node node,
173: InternalContextAdapter context, Writer writer)
174: throws IOException, MethodInvocationException,
175: ResourceNotFoundException {
176: if (node == null) {
177: rsvc.getLog().error("#include() null argument");
178: return false;
179: }
180:
181: /*
182: * does it have a value? If you have a null reference, then no.
183: */
184: Object value = node.value(context);
185: if (value == null) {
186: rsvc.getLog().error("#include() null argument");
187: return false;
188: }
189:
190: /*
191: * get the path
192: */
193: String sourcearg = value.toString();
194:
195: /*
196: * check to see if the argument will be changed by the event handler
197: */
198:
199: String arg = EventHandlerUtil.includeEvent(rsvc, context,
200: sourcearg, context.getCurrentTemplateName(), getName());
201:
202: /*
203: * a null return value from the event cartridge indicates we should not
204: * input a resource.
205: */
206: boolean blockinput = false;
207: if (arg == null)
208: blockinput = true;
209:
210: Resource resource = null;
211:
212: try {
213: if (!blockinput)
214: resource = rsvc.getContent(arg,
215: getInputEncoding(context));
216: } catch (ResourceNotFoundException rnfe) {
217: /*
218: * the arg wasn't found. Note it and throw
219: */
220: rsvc.getLog().error(
221: "#include(): cannot find resource '" + arg
222: + "', called from template "
223: + context.getCurrentTemplateName()
224: + " at (" + getLine() + ", " + getColumn()
225: + ")");
226: throw rnfe;
227: }
228:
229: /**
230: * pass through application level runtime exceptions
231: */
232: catch (RuntimeException e) {
233: throw e;
234: } catch (Exception e) {
235: rsvc.getLog().error(
236: "#include(): arg = '" + arg
237: + "', called from template "
238: + context.getCurrentTemplateName()
239: + " at (" + getLine() + ", " + getColumn()
240: + ')', e);
241: }
242:
243: /*
244: * note - a blocked input is still a successful operation as this is
245: * expected behavior.
246: */
247:
248: if (blockinput)
249: return true;
250:
251: else if (resource == null)
252: return false;
253:
254: writer.write((String) resource.getData());
255: return true;
256: }
257:
258: /**
259: * Puts a message to the render output stream if ERRORMSG_START / END
260: * are valid property strings. Mainly used for end-user template
261: * debugging.
262: * @param writer
263: * @param msg
264: * @throws IOException
265: */
266: private void outputErrorToStream(Writer writer, String msg)
267: throws IOException {
268: if (outputMsgStart != null && outputMsgEnd != null) {
269: writer.write(outputMsgStart);
270: writer.write(msg);
271: writer.write(outputMsgEnd);
272: }
273: }
274: }
|