Creating Custom Tools for Apache Velocity

JavaVelocity

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

How to Force Build With FIXME Blocks to Fail in Ant

JavaAnt

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.

Sep 1, 2009

How to Integrate Velocity Tools 2.0 with Spring

JavaVelocitySpring

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().

Aug 26, 2009

How to Write Many-To-Many Search Queries in MySQL and Hibernate

JavaMySQLHibernate

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:

Table Schema

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.

Aug 21, 2009

Guide to Selecting Appropriate Map/Collection in Java

Java

Java API contains numerous Collection and Map implementations so it might be confusing to figure out which one to use. Here is a quick flowchart that might help with choosing from the most common implementations.

This cheat sheet doesn't include rarely used classes like WeakHashMap, LinkedList, etc. because they are designed for very specific or exotic tasks and shouldn't be chosen in 99% cases. Java Map/Collection Cheat Sheet

If you are interested in other built-in collection implementations, Alexander Zagniotov provided a detailed flowchart for many more collections in his article.

Aug 14, 2009
profile for serg at Stack Overflow, Q&A for professional and enthusiast programmers