All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.velocity.tools.generic.RenderTool Maven / Gradle / Ivy

Go to download

Generic tools that can be used in any context. PLEASE NOTE: this is a temporary fork to unblock projects migrating to Jakarta, but I won't continue maintaining it in the future as the Velocity team doesn't understand the value of Jakarta. I strongly suggest you plan a switch to a more modern template engine such as Thymeleaf.

The newest version!
package org.apache.velocity.tools.generic;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.config.InvalidScope;

/**
 * This tool exposes methods to evaluate the given
 * strings as VTL (Velocity Template Language)
 * using either a pre-configured context or one you
 * provide directly.
 * 
 * Example of eval():
 *      Input
 *      -----
 *      #set( $list = [1,2,3] )
 *      #set( $object = '$list' )
 *      #set( $method = 'size()' )
 *      $render.eval("${object}.$method")
 *
 *      Output
 *      ------
 *      3
 *
 * Example of recurse():
 *      Input
 *      -----
 *      #macro( say_hi )hello world!#end
 *      #set( $foo = '#say_hi()' )
 *      #set( $bar = '$foo' )
 *      $render.recurse($bar)
 *
 *      Output
 *      ------
 *      hello world!
 *
 *
 * Toolbox configuration:
 * <tools>
 *   <toolbox scope="request">
 *     <tool class="org.apache.velocity.tools.generic.RenderTool">
 *       <property name="parseDepth" type="number" value="10"/>
 *     </tool>
 *   </toolbox>
 * </tools>
 * 
* *

Ok, so these examples are really lame. But, it seems like * someone out there is always asking how to do stuff like this * and we always tell them to write a tool. Now we can just tell * them to use this tool.

* *

This tool may be used in any scope, however, the context provided * for the {@link #eval(String)} and {@link #recurse(String)} methods * will only be current if the tool is request scoped. If application or * session scoped, then the context will be the same one set at the time * of the tool's first use. In such a case, each call to eval(String) or * recurse(String) will by default create a new Context that wraps the * configured one to prevent modifications to the configured Context * (concurrent or otherwise). If you wish to risk it and accrete changes * then you can relax the thread-safety by setting the 'forceThreadSafe' * property to 'false'.

* *

Of course none of the previous paragraph likely applies if you are * not using the core tool management facilities or if you stick to the * {@link #eval(Context,String)} and {@link #recurse(Context,String)} * methods. :)

* *

This tool by default will catch * and log any exceptions thrown during rendering and * instead return null in such cases. It also limits recursion, by default, * to 20 cycles, to prevent infinite loops. Both settings may be configured * to behave otherwise.

* * @author Nathan Bubna * @version $Revision$ $Date$ */ @DefaultKey("render") @InvalidScope(Scope.SESSION) public class RenderTool extends SafeConfig { /** * The maximum number of loops allowed when recursing. * @since VelocityTools 1.2 */ public static final int DEFAULT_PARSE_DEPTH = 20; @Deprecated public static final String KEY_PARSE_DEPTH = "parse.depth"; @Deprecated public static final String KEY_CATCH_EXCEPTIONS = "catch.exceptions"; public static final String KEY_FORCE_THREAD_SAFE = "forceThreadSafe"; private VelocityEngine engine = null; private Context context; private int parseDepth = DEFAULT_PARSE_DEPTH; private boolean catchExceptions = true; private boolean forceThreadSafe = true; /** * Looks for deprecated parse depth and catch.exceptions properties, * as well as any 'forceThreadSafe' setting. */ protected void configure(ValueParser parser) { // look for deprecated parse.depth key Integer depth = parser.getInteger(KEY_PARSE_DEPTH); if (depth != null) { setParseDepth(depth); } // look for deprecated catch.exceptions key Boolean catchEm = parser.getBoolean(KEY_CATCH_EXCEPTIONS); if (catchEm != null) { setCatchExceptions(catchEm); } // check if they want thread-safety manually turned off this.forceThreadSafe = parser.getBoolean(KEY_FORCE_THREAD_SAFE, forceThreadSafe); // if we're request-scoped, then there's no point in forcing the issue if (Scope.REQUEST.equals(parser.getString("scope"))) { this.forceThreadSafe = false; } } /** * Allow user to specify a VelocityEngine to be used * in place of the Velocity singleton. * @param ve VelocityEngine instance */ public void setVelocityEngine(VelocityEngine ve) { this.engine = ve; } /** * Set the maximum number of loops allowed when recursing. * @param depth parse depth * @since VelocityTools 1.2 */ public void setParseDepth(int depth) { if (!isConfigLocked()) { this.parseDepth = depth; } else if (this.parseDepth != depth) { getLog().error("Attempt was made to alter parse depth while config was locked."); } } /** * Sets the {@link Context} to be used by the {@link #eval(String)} * and {@link #recurse(String)} methods. * @param context Velocity context */ public void setVelocityContext(Context context) { if (!isConfigLocked()) { if (context == null) { throw new NullPointerException("context must not be null"); } this.context = context; } else if (this.context != context) { getLog().error("Attempt was made to set a new context while config was locked."); } } /** * Get the maximum number of loops allowed when recursing. * @return parse depth * @since VelocityTools 1.2 */ public int getParseDepth() { return this.parseDepth; } /** * Sets whether or not the render() and eval() methods should catch * exceptions during their execution or not. * @param catchExceptions whether to catch exceptions * @since VelocityTools 1.3 */ public void setCatchExceptions(boolean catchExceptions) { if (!isConfigLocked()) { this.catchExceptions = catchExceptions; } else if (this.catchExceptions != catchExceptions) { getLog().error("Attempt was made to alter catchE while config was locked."); } } /** * Returns true if this render() and eval() methods will * catch exceptions thrown during rendering. * @return whether to catch exceptions * @since VelocityTools 1.3 */ public boolean getCatchExceptions() { return this.catchExceptions; } /** *

Evaluates a String containing VTL using the context passed * to the {@link #setVelocityContext} method. If this tool is request * scoped, then this will be the current context and open to modification * by the rendered VTL. If application or session scoped, the context * will be a new wrapper around the configured context to protect it * from modification. * The results of the rendering are returned as a String. By default, * null will be returned when this throws an exception. * This evaluation is not recursive.

* * @param vtl the code to be evaluated * @return the evaluated code as a String * @throws Exception if womething went wrong */ public String eval(String vtl) throws Exception { Context ctx = forceThreadSafe ? new VelocityContext(context) : context; return eval(ctx, vtl); } /** *

Recursively evaluates a String containing VTL using the * current context, and returns the result as a String. It * will continue to re-evaluate the output of the last * evaluation until an evaluation returns the same code * that was fed into it.

* * @see #eval(String) * @param vtl the code to be evaluated * @return the evaluated code as a String * @throws Exception if womething went wrong */ public String recurse(String vtl) throws Exception { Context ctx = forceThreadSafe ? new VelocityContext(context) : context; return recurse(ctx, vtl); } /** *

Evaluates a String containing VTL using the current context, * and returns the result as a String. By default if this fails, then * null will be returned, though this tool can be configured * to let Exceptions pass through. This evaluation is not recursive.

* * @param ctx the current Context * @param vtl the code to be evaluated * @return the evaluated code as a String * @throws Exception if womething went wrong */ public String eval(Context ctx, String vtl) throws Exception { if (this.catchExceptions) { try { return internalEval(ctx, vtl); } catch (Exception e) { getLog().error("evaluation failed:", e); return null; } } else { return internalEval(ctx, vtl); } } /* Internal implementation of the eval() method function. */ protected String internalEval(Context ctx, String vtl) throws Exception { if (vtl == null) { return null; } StringWriter sw = new StringWriter(); boolean success; if (engine == null) { success = Velocity.evaluate(ctx, sw, "RenderTool.eval()", vtl); } else { success = engine.evaluate(ctx, sw, "RenderTool.eval()", vtl); } if (success) { return sw.toString(); } /* or would it be preferable to return the original? */ return null; } /** *

Recursively evaluates a String containing VTL using the * current context, and returns the result as a String. It * will continue to re-evaluate the output of the last * evaluation until an evaluation returns the same code * that was fed into it or the number of recursive loops * exceeds the set parse depth.

* * @param ctx the current Context * @param vtl the code to be evaluated * @return the evaluated code as a String * @throws Exception if womething went wrong */ public String recurse(Context ctx, String vtl) throws Exception { return internalRecurse(ctx, vtl, 0); } protected String internalRecurse(Context ctx, String vtl, int count) throws Exception { String result = eval(ctx, vtl); if (result == null || result.equals(vtl)) { return result; } else { // if we haven't reached our parse depth... if (count < parseDepth) { // continue recursing return internalRecurse(ctx, result, count + 1); } else { // abort, log and return what we have so far getLog().error("recursion exceeded the maximum parse depth" + " of {} on the following template: {}", parseDepth, vtl); return result; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy