001: package org.apache.velocity.tools.generic;
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.StringWriter;
023: import java.util.Map;
024: import org.apache.velocity.app.Velocity;
025: import org.apache.velocity.app.VelocityEngine;
026: import org.apache.velocity.context.Context;
027:
028: /**
029: * This tool exposes methods to evaluate the given
030: * strings as VTL (Velocity Template Language)
031: * using the given context.
032: * <p>
033: * NOTE: These examples assume you have placed an
034: * instance of the current context within itself
035: * as 'ctx'. And, of course, the RenderTool is
036: * assumed to be available as 'render'.
037: * </p>
038: * <pre>
039: * Example of eval():
040: * Input
041: * -----
042: * #set( $list = [1,2,3] )
043: * #set( $object = '$list' )
044: * #set( $method = 'size()' )
045: * $render.eval($ctx, "${object}.$method")
046: *
047: * Output
048: * ------
049: * 3
050: *
051: * Example of recurse():
052: * Input
053: * -----
054: * #macro( say_hi )hello world!#end
055: * #set( $foo = '#say_hi()' )
056: * #set( $bar = '$foo' )
057: * $render.recurse($ctx, $bar)
058: *
059: * Output
060: * ------
061: * hello world!
062: *
063: *
064: * Toolbox configuration:
065: * <tool>
066: * <key>render</key>
067: * <class>org.apache.velocity.tools.generic.RenderTool</class>
068: * </tool>
069: * </pre>
070: *
071: * <p>Ok, so these examples are really lame. But, it seems like
072: * someone out there is always asking how to do stuff like this
073: * and we always tell them to write a tool. Now we can just tell
074: * them to use this tool.</p>
075: *
076: * <p>This tool is safe (and optimized) for use in the application
077: * scope of a servlet environment.</p>
078: *
079: * @author Nathan Bubna
080: * @version $Revision: 484711 $ $Date: 2006-12-08 11:41:50 -0800 (Fri, 08 Dec 2006) $
081: */
082: public class RenderTool {
083: /**
084: * The maximum number of loops allowed when recursing.
085: * @since VelocityTools 1.2
086: */
087: public static final int DEFAULT_PARSE_DEPTH = 20;
088: public static final String KEY_PARSE_DEPTH = "parse.depth";
089: public static final String KEY_CATCH_EXCEPTIONS = "catch.exceptions";
090:
091: private static final String LOG_TAG = "RenderTool.eval()";
092:
093: private VelocityEngine engine = null;
094: private int parseDepth = DEFAULT_PARSE_DEPTH;
095: private boolean catchExceptions = true;
096:
097: /**
098: * Looks for parse depth and catch.exceptions parameters.
099: * @since VelocityTools 1.3
100: */
101: public void configure(Map params) {
102: ValueParser parser = new ValueParser(params);
103: int depth = parser.getInt(KEY_PARSE_DEPTH, DEFAULT_PARSE_DEPTH);
104: setParseDepth(depth);
105:
106: boolean catchEm = parser.getBoolean(KEY_CATCH_EXCEPTIONS, true);
107: setCatchExceptions(catchEm);
108: }
109:
110: /**
111: * Allow user to specify a VelocityEngine to be used
112: * in place of the Velocity singleton.
113: */
114: public void setVelocityEngine(VelocityEngine ve) {
115: this .engine = ve;
116: }
117:
118: /**
119: * Set the maximum number of loops allowed when recursing.
120: *
121: * @since VelocityTools 1.2
122: */
123: public void setParseDepth(int depth) {
124: this .parseDepth = depth;
125: }
126:
127: /**
128: * Get the maximum number of loops allowed when recursing.
129: *
130: * @since VelocityTools 1.2
131: */
132: public int getParseDepth() {
133: return this .parseDepth;
134: }
135:
136: /**
137: * Sets whether or not the render() and eval() methods should catch
138: * exceptions during their execution or not.
139: * @since VelocityTools 1.3
140: */
141: public void setCatchExceptions(boolean catchExceptions) {
142: this .catchExceptions = catchExceptions;
143: }
144:
145: /**
146: * Returns <code>true</code> if this render() and eval() methods will
147: * catch exceptions thrown during rendering.
148: * @since VelocityTools 1.3
149: */
150: public boolean getCatchExceptions() {
151: return this .catchExceptions;
152: }
153:
154: /**
155: * <p>Evaluates a String containing VTL using the current context,
156: * and returns the result as a String. By default if this fails, then
157: * <code>null</code> will be returned, though this tool can be configured
158: * to let Exceptions pass through. This evaluation is not recursive.</p>
159: *
160: * @param ctx the current Context
161: * @param vtl the code to be evaluated
162: * @return the evaluated code as a String
163: */
164: public String eval(Context ctx, String vtl) throws Exception {
165: if (this .catchExceptions) {
166: try {
167: return internalEval(ctx, vtl);
168: } catch (Exception e) {
169: String msg = LOG_TAG + " threw Exception: " + e;
170: if (engine == null) {
171: Velocity.debug(msg);
172: } else {
173: engine.debug(msg);
174: }
175: return null;
176: }
177: } else {
178: return internalEval(ctx, vtl);
179: }
180: }
181:
182: /* Internal implementation of the eval() method function. */
183: private String internalEval(Context ctx, String vtl)
184: throws Exception {
185: if (vtl == null) {
186: return null;
187: }
188: StringWriter sw = new StringWriter();
189: boolean success;
190: if (engine == null) {
191: success = Velocity.evaluate(ctx, sw, LOG_TAG, vtl);
192: } else {
193: success = engine.evaluate(ctx, sw, LOG_TAG, vtl);
194: }
195: if (success) {
196: return sw.toString();
197: }
198: /* or would it be preferable to return the original? */
199: return null;
200: }
201:
202: /**
203: * <p>Recursively evaluates a String containing VTL using the
204: * current context, and returns the result as a String. It
205: * will continue to re-evaluate the output of the last
206: * evaluation until an evaluation returns the same code
207: * that was fed into it or the number of recursive loops
208: * exceeds the set parse depth.</p>
209: *
210: * @param ctx the current Context
211: * @param vtl the code to be evaluated
212: * @return the evaluated code as a String
213: */
214: public String recurse(Context ctx, String vtl) throws Exception {
215: return internalRecurse(ctx, vtl, 0);
216: }
217:
218: protected String internalRecurse(Context ctx, String vtl, int count)
219: throws Exception {
220: String result = eval(ctx, vtl);
221: if (result == null || result.equals(vtl)) {
222: return result;
223: } else {
224: // if we haven't reached our parse depth...
225: if (count < parseDepth) {
226: // continue recursing
227: return internalRecurse(ctx, result, count++);
228: } else {
229: // abort and return what we have so far
230: //FIXME: notify the developer or user somehow??
231: return result;
232: }
233: }
234: }
235:
236: }
|