Creating Custom Directives for Apache Velocity
In this article we will go over Apache Velocity API and write our own inline and block directives. Like in the previous article about creating Velocity tools, we will be trying to duplicate the behavior of truncate() method from DisplayTools which truncates a long string.
There are two directive types in Velocity - inline and block directives. Inline directives consist from a single line, while block directives have body and closing tag #end:
#include( "one.gif","two.txt","three.htm" )
#if( $foo )
Block directive...
#end
Creating inline directive
To declare your custom directive you need to specify userdirective parameter in velocity config (for example in velocity.properties file) and provide comma separated list of complete user directive class names:
userdirective=com.example.MyDirective1, com.example.MyDirective2
Lets create our first inline directive #truncate with 4 parameters:
#truncate(Object truncateMe, int maxLength, String suffix, boolean truncateAtWord)
First parameter contains a string we want to truncate and is the only required parameter, the other 3 set optional truncating settings.
Lets look at the final directive code and then go over it in details:
package ca.sergiy.velocity;
import java.io.IOException;
import java.io.Writer;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.parser.node.Node;
public class TruncateDirective extends Directive {
public String getName() {
return "truncate";
}
public int getType() {
return LINE;
}
public boolean render(InternalContextAdapter context, Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
//setting default params
String truncateMe = null;
int maxLength = 10;
String suffix = null;
Boolean truncateAtWord = false;
//reading params
if (node.jjtGetChild(0) != null) {
truncateMe = String.valueOf(node.jjtGetChild(0).value(context));
}
if (node.jjtGetChild(1) != null) {
maxLength = (Integer)node.jjtGetChild(1).value(context);
}
if (node.jjtGetChild(2) != null) {
suffix = String.valueOf(node.jjtGetChild(2).value(context));
}
if (node.jjtGetChild(3) != null) {
truncateAtWord = (Boolean)node.jjtGetChild(3).value(context);
}
//truncate and write result to writer
writer.write(truncate(truncateMe, maxLength, suffix, truncateAtWord));
return true;
}
//does actual truncating (taken directly from DisplayTools)
public 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;
}
}
All directives in Apache extend Directive class that has 3 abstract methods we need to implement: getName(), getType() and render().
First method getName() should return a name of your directive that will be used in templates.
Method getType() returns BLOCK or LINE constants which determine a directive type.
Third method render(InternalContextAdapter context, Writer writer, Node node) is where all the work is happening. Writer is our template writer where we are going to write the result. Node object contains information about our directive (its parameters and properties) and InternalContextAdapter contains everything Velocity needs to know about the template in order to render it.
Directive parameters (or, more precisely, nodes, representing parameters) can be accessed from directive node by calling node.jjtGetChild(i), where i is parameter number (starting from zero). You can get total number of parameters using node.jjtGetNumChildren().
jjtGetChild(i) method returns Node object, from which you can either get "rendered" value by calling node.jjtGetChild(i).value() method, or retrieve its literal value by calling node.jjtGetChild(i).literal(). To demonstrate the difference between rendered and literal values lets take a look at this template:
#set($test = "Test Value")
#truncate("This is $test")
(String)node.jjtGetChild(0).value() will return "This is Test Value", while (String)node.jjtGetChild(0).literal() will return "This is $test". In most cases you will need rendered values.
Lets test our newly created directive. First we need to declare it in config file so Velocity could find it:
userdirective=ca.sergiy.velocity.TruncateDirective
Now lets run this template:
#set($test = "long line that should be truncated")
#truncate("Testing $test", 20, "...", true)
Result:
Testing long line...
Creating block directive
Now lets turn our inline directive into block directive:
#truncateBlock(int maxLength, String suffix, boolean truncateAtWord)
Long block
that will be
truncated
#end
It will work the same as before with all parameters being optional. Lets look at the code:
package ca.sergiy.velocity;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.parser.node.Node;
public class TruncateBlockDirective extends Directive {
private Log log;
private int maxLength;
private String suffix;
private Boolean truncateAtWord;
public String getName() {
return "truncateBlock";
}
public int getType() {
return BLOCK;
}
@Override
public void init(RuntimeServices rs, InternalContextAdapter context, Node node) throws TemplateInitException {
super.init(rs, context, node);
log = rs.getLog();
//read dafault values from config
maxLength = rs.getInt("userdirective.truncateBlock.maxLength", 10);
suffix = rs.getString("userdirective.truncateBlock.suffix", "...");
truncateAtWord = rs.getBoolean("userdirective.truncateBlock.truncateAtWord", false);
}
public boolean render(InternalContextAdapter context, Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
log.debug("truncateBlock directive render() call");
String truncateMe = null;
//default settings
int maxLength = this.maxLength;
String suffix = this.suffix;
Boolean truncateAtWord = this.truncateAtWord;
//loop through all "params"
for(int i=0; i<node.jjtGetNumChildren(); i++) {
if (node.jjtGetChild(i) != null ) {
if(!(node.jjtGetChild(i) instanceof ASTBlock)) {
//reading and casting inline parameters
if(i == 0) {
maxLength = (Integer)node.jjtGetChild(i).value(context);
} else if(i == 1) {
suffix = String.valueOf(node.jjtGetChild(i).value(context));
} else if(i == 2) {
truncateAtWord = (Boolean)node.jjtGetChild(i).value(context);
} else {
break;
}
} else {
//reading block content and rendering it
StringWriter blockContent = new StringWriter();
node.jjtGetChild(i).render(context, blockContent);
truncateMe = blockContent.toString();
break;
}
}
}
//truncate and write result to writer
try {
writer.write(truncate(truncateMe, maxLength, suffix, truncateAtWord));
} catch (Exception e) {
String msg = "Truncate failed";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
return true;
}
//does actual truncating (taken directly from DisplayTools)
public 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;
}
}
First difference is that we additionally overrode init() method from Directive class that allows us to get access to RuntimeServices object in order to get logger instance and read default directive parameters from configuration file. So now our velocity.properties file looks like this:
userdirective=ca.sergiy.velocity.TruncateDirective, ca.sergiy.velocity.TruncateBlockDirective
userdirective.truncateBlock.maxLength=10
userdirective.truncateBlock.suffix=...
userdirective.truncateBlock.truncateAtWord=false
Setting all default parameter values in config is optional, because if parameter is not present we will still assign hardcoded default value. For example this line sets maxLength to userdirective.truncateBlock.maxLength value if present, otherwise it is set to 10:
maxLength = rs.getInt("userdirective.truncateBlock.maxLength", 10);
There is no official recommendations for config parameter naming for user directives, but using userdirective.{directive}.{param} format should be good choice.
render() method looks a little different this time as well. The reason for this is that block content is passed as node's last child (after other inline parameters), but because all our parameters are optional we need to somehow separate block content from other inline parameters. One way of doing this is to check chlid node's class name. For block content it is always ASTBlock, which is different from the rest. In order to render ASTBlock value we have to use render(InternalContextAdapter context, Writer writer) method that will render it to a provided writer (as opposing to simply calling node.value() for inline parameters).
Conclusion
In this article we looked at creating custom user directives for Apache Velocity. But before writing your own Velocity directive you should consider using tools approach instead if possible, as writing custom directives should be reserved for extending core template language or working with multiline parameters. For example if "truncate" method was not present in DispalyTools already, proper way of doing it would be creating new Tool class and adding it there instead of making it a directive.
Creating custom directives is not too complicated, but they are not well documented and it is hard to find any info or tutorials about them. If you need more examples take a look at Velocity built-in directive sources. There are some simple directive examples on VelocityTools wiki. You can also check out my open-source htmlcompressor project that has some block directives.
Creating Custom Tools for Apache 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.
How to Force Build With FIXME Blocks to Fail in Ant
If you need to attach a reminder to some particular piece of code in order to change or fix it later, you can mark it with //XXX, //TODO, //FIXME comments. Even though most IDEs can recognize these comments and add them to a task list, it is still easy to forget about their existence as nothing forces you to go over that list. If you are looking for some stronger solution, you can easily use Ant to fail a build if it contains some specific comment marks.
Here is Ant script that fails a build if sources contain //FIXME comment:
<target name="fixmeCheck" if="productionBuild">
<fail message="FIXME block found.">
<condition>
<not>
<resourcecount count="0">
<fileset dir="./src/" includes="**/*.java">
<contains text="//FIXME" casesensitive="yes"/>
</fileset>
</resourcecount>
</not>
</condition>
</fail>
</target>
This solution works well if you have two builds - for local development and for production server, as you don't want to fail your local build every time, only production.
Now if you are experimenting with something or testing some changes, you can just mark that piece of code with //FIXME comment and be sure that you won't forget about it when making a production build.
How to Integrate Velocity Tools 2.0 with Spring
Spring Framework has built-in Apache Velocity support but it is outdated, uses some now deprecated classes and doesn't support Velocity Tools 2.0 sub-project that contains great amount of useful goodies (it is still in beta but already bulletproof enough for production). Here is an example how to integrate Velocity Tools 2.0 into Spring.
What we need to do is extend VelocityView class and override createVelocityContext() method. Here is our extended class VelocityToolsView:
package ca.sergiy.velocity;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.ToolContext;
import org.apache.velocity.tools.ToolManager;
import org.springframework.web.servlet.view.velocity.VelocityView;
public class VelocityToolsView extends VelocityView {
private static ToolContext toolContext = initVelocityToolContext();
@Override
protected Context createVelocityContext(Map model, HttpServletRequest request, HttpServletResponse response) {
VelocityContext context = new VelocityContext(toolContext);
if(model != null) {
for(Map.Entry<String, Object> entry : (Set<Map.Entry<String, Object>>)model.entrySet()) {
context.put(entry.getKey(), entry.getValue());
}
}
return context;
}
private static ToolContext initVelocityToolContext() {
if(toolContext == null) {
ToolManager velocityToolManager = new ToolManager();
velocityToolManager.configure("velocity-tools.xml");
toolContext = velocityToolManager.createContext();
}
return toolContext;
}
}
Now you can just use this class anywhere instead of default VelocityView and enjoy all benefits of Velocity Tools 2.0.
For performance reasons ToolContext has static initializer so it is getting created only once. If you need to pass some configuration parameters for every instance then just put initialization directly into initVelocityToolContext().
How to Write Many-To-Many Search Queries in MySQL and Hibernate
Lets review basic many-to-many relationship between tables and build common search queries in MySQL and Hibernate HQL. We will take this site's database schema which has article-tag many-to-many relationship as an example and will try to build search queries to find articles by specific tags.
Here is our database schema that implements many-to-many relationship using intermediate "article_tag" table:

Corresponding Hibernate mappings:
<hibernate-mapping>
<class name=ca.sergiy.model.Tag" table="tag">
<cache usage="read-write"/>
<id name="id" column="id" type="long">
<generator class="native"/>
</id>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="ca.sergiy.model.Article" table="article">
<cache usage="read-write" />
<id name="id" column="id" type="long">
<generator class="native" />
</id>
<property name="title" column="title" />
<set name="tags" table="article_tag" lazy="false">
<key column="articleid" />
<many-to-many class="ca.sergiy.model.Tag" column="tagid" />
</set>
</class>
</hibernate-mapping>
#1. Find all articles that are tagged with any of tag1, tag2, ..., tagn
MySQL query to select all articles that have "Java" or "Hibernate" among their assigned tags:
SELECT DISTINCT a.*
FROM `article` a
INNER JOIN article_tag at
ON at.articleid = a.id
INNER JOIN tag t
ON t.id = at.tagid
WHERE t.name IN ("Java", "Hibernate")
Corresponding Hibernate HQL:
String[] tags = {"Java", "Hibernate"};
String hql = "select distinct a from Article a " +
"join a.tags t " +
"where t.name in (:tags)";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
List<Article> articles = query.list();
This query will also work for a single tag (find all articles tagged with "Java")
#2. Find all articles that have no tags assigned
MySQL query:
SELECT a.*
FROM `article` a
LEFT JOIN article_tag at
ON at.articleid = a.id
GROUP BY a.id
HAVING Count(at.tagid) = 0
Corresponding Hibernate HQL:
String hql = "select a from Article a " +
"left join a.tags t " +
"group by a " +
"having count(t)=0";
Query query = session.createQuery(hql);
List<Article> articles = query.list();
Note that this query uses LEFT JOIN.
#3. Find all articles that are tagged with at least tag1, tag2, ..., tagn
MySQL query to select all articles that have at least both "Java" and "Hibernate" among their assigned tags:
SELECT a.*
FROM article a
INNER JOIN (SELECT at.articleid
FROM article_tag at
INNER JOIN article a
ON a.id = at.articleid
INNER JOIN tag t
ON t.id = at.tagid
WHERE t.name IN ("Java","Hibernate")
GROUP BY at.articleid
HAVING Count(at.articleid) = 2) aa
ON a.id = aa.articleid
Hibernate HQL, looks much cleaner:
String[] tags = {"Java", "Hibernate"};
String hql = "select a from Article a " +
"join a.tags t " +
"where t.name in (:tags) " +
"group by a " +
"having count(t)=:tag_count";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
query.setInteger("tag_count", tags.length);
List<Article> articles = query.list();
#4. Find all articles that are tagged with exactly tag1, tag2, ..., tagn
MySQL query to select all articles that are tagged with exactly "Java" and "Hibernate" tags (no other tags assigned):
SELECT a.*
FROM article a
INNER JOIN (SELECT at.articleid
FROM article_tag at
WHERE at.articleid IN (SELECT at2.articleid
FROM article_tag at2
INNER JOIN article a2
ON a2.id = at2.articleid
GROUP BY at2.articleid
HAVING Count(at2.articleid) = 2)
AND at.tagid IN (SELECT id
FROM tag t
WHERE t.name IN ("Java","Hibernate"))
GROUP BY at.articleid
HAVING Count(at.articleid) = 2) aa
ON a.id = aa.articleid
Hibernate HQL:
String[] tags = {"Java", "Hibernate"};
String hql = "select a from Article a " +
"join a.tags t " +
"where t.name in (:tags) " +
"and a.id in (" +
"select a2.id " +
"from Article a2 " +
"join a2.tags t2 " +
"group by a2 " +
"having count(t2)=:tag_count) " +
"group by a " +
"having count(t)=:tag_count";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
query.setInteger("tag_count", tags.length);
List<Article> articles = query.list();
Basically it is query #3 with extra condition applied: total number of tags should be n.