Creating Custom Tools for Apache Velocity

Java Velocity

Apache Velocity Tools project has been drastically improved and extended in version 2.0 and now it comes with a wide variety of useful utilities and helpers like DisplayTool, NumberTool and many more. Today we will look into creating our own custom tools and utility classes for Apache Velocity.

Our goal will be to write a utility with truncate() method which we can use inside Velocity templates and which simply truncates a long string. The behavior will be exactly the same as existing truncate() method from DisplayTools.

Using utility class approach

Lets start by creating a regular utility class with our static method:

package ca.sergiy.velocity;

public class TestUtil {

    //taken directly from DisplayTools
    public static String truncate(String truncateMe, int maxLength, String suffix, boolean truncateAtWord) {
        if (truncateMe == null || maxLength <= 0) {
            return null;
        }

        if (truncateMe.length() <= maxLength) {
            return truncateMe;
        }
        if (suffix == null || maxLength - suffix.length() <= 0) {
            // either no need or no room for suffix
            return truncateMe.substring(0, maxLength);
        }
        if (truncateAtWord) {
            // find the latest space within maxLength
            int lastSpace = truncateMe.substring(0, maxLength - suffix.length() + 1).lastIndexOf(" ");
            if (lastSpace > suffix.length()) {
                return truncateMe.substring(0, lastSpace) + suffix;
            }
        }
        // truncate to exact character and append suffix
        return truncateMe.substring(0, maxLength - suffix.length()) + suffix;

    }

}

The easiest way of letting specific template access our utility class would be to just add an instance of this class to a template:

VelocityContext context = //create velocity context
context.put("testUtil", new TestUtil());

Now we can use truncate() method in the template:

$testUtil.truncate("Long line that should be truncated", 10, "...", true)

The advantage of this method is that we can use any existing utility class without any modifications, but the drawback is that we need to manually put an object of this class into every template that will be using it. To expose our utility methods to all templates in the project by default we need to write our own tool class.

Creating custom tool class

Lets look at the code of our custom tool and then go over it:

package ca.sergiy.velocity;

import org.apache.velocity.tools.config.DefaultKey;

@DefaultKey("testTool")
public class TestTool {

    //taken directly from DisplayTools
    public static String truncate(String truncateMe, int maxLength, String suffix, boolean truncateAtWord) {
        if (truncateMe == null || maxLength <= 0) {
            return null;
        }

        if (truncateMe.length() <= maxLength) {
            return truncateMe;
        }
        if (suffix == null || maxLength - suffix.length() <= 0) {
            // either no need or no room for suffix
            return truncateMe.substring(0, maxLength);
        }
        if (truncateAtWord) {
            // find the latest space within maxLength
            int lastSpace = truncateMe.substring(0, maxLength - suffix.length() + 1).lastIndexOf(" ");
            if (lastSpace > suffix.length()) {
                return truncateMe.substring(0, lastSpace) + suffix;
            }
        }
        // truncate to exact character and append suffix
        return truncateMe.substring(0, maxLength - suffix.length()) + suffix;

    }

}

This class looks almost exactly the same as our regular utility class except one annotation @DefaultKey("testTool") which sets a name of this tool that will be used in templates:

$testTool.truncate("Long line that should be truncated", 10, "...", true)

The only thing left to do is to register this tool to let Velocity know about its existence. We need to edit velocity-tools.xml and add our tool class:

<tools> 
    <data type="number" key="TOOLS_VERSION" value="2.0"/>
    <data type="boolean" key="GENERIC_TOOLS_AVAILABLE" value="true"/>
    <toolbox scope="application">

        <!-- Our tool class -->
        <tool class="ca.sergiy.velocity.TestTool"/>

        <tool class="org.apache.velocity.tools.generic.AlternatorTool"/>
        <tool class="org.apache.velocity.tools.generic.DisplayTool"/>
        <tool class="org.apache.velocity.tools.generic.MathTool"/>
        <tool class="org.apache.velocity.tools.generic.NumberTool"/>
        <tool class="org.apache.velocity.tools.generic.ComparisonDateTool"/>
        <tool class="org.apache.velocity.tools.generic.ClassTool"/>
        <tool class="org.apache.velocity.tools.generic.ConversionTool"/>
        <tool class="org.apache.velocity.tools.generic.EscapeTool"/>
        <tool class="org.apache.velocity.tools.generic.FieldTool"/>
        <tool class="org.apache.velocity.tools.generic.ListTool"/>
        <tool class="org.apache.velocity.tools.generic.ResourceTool"/>
        <tool class="org.apache.velocity.tools.generic.SortTool"/>
    </toolbox>
    <toolbox scope="request">
        <tool class="org.apache.velocity.tools.generic.LoopTool"/>
        <tool class="org.apache.velocity.tools.generic.ContextTool"/>
        <tool class="org.apache.velocity.tools.generic.LinkTool"/>
        <tool class="org.apache.velocity.tools.generic.RenderTool"/>
    </toolbox>
</tools>

So our velocity context initialization would look like this now:

ToolManager velocityToolManager = new ToolManager();
velocityToolManager.configure("velocity-tools.xml");
VelocityContext context = new VelocityContext(velocityToolManager.createContext());

Conclusion

As you can see exposing an existing utility class to a velocity templates is extremely easy and doesn't require any code modifications. Even creating your own tool classes was made as easy as possible with bare minimum of modifications. In the next article we will look into creating custom Velocity directives.

Sep 4, 2009
profile for serg at Stack Overflow, Q&A for professional and enthusiast programmers