How to #include a file from a Velocity template using ClasspathResourceLoader

SantiBailors picture SantiBailors · Feb 12, 2014 · Viewed 24.2k times · Source

I'm dealing with some Java code where Velocity 1.7 is set to retrieve templates through the ClasspathResourceLoader. Below is a stripped down sample of the code. It's from a Tapestry web application running on a Jetty server.

The Java class, the templates and the file to be included are all are in the same folder "testpackage", so in the resulting JAR they are all in the same package "testpackage".

The problem is that if the template contains an

#include("MyInclude.vm")

directive, Velocity cannot find "MyInclude.vm", and it throws a ResourceNotFoundException.

Since in the argument of getTemplate I must prepend the package name to the template name, I also tried to do the same in the #include inside the template:

#include("testpackage/MyInclude.vm")

but the only difference is that the latter works if I run the web app from Eclipse, while the former doesn't work not even from Eclipse. If I build, deploy the JARs and run the web app from the deployment, both syntaxes fail with the same ResourceNotFoundException.

The Velocity doc at http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html#Include says:

"For security reasons, the file to be included may only be under TEMPLATE_ROOT"

This definitely could be the cause of my problem, but I haven't been able to find any further information about what TEMPLATE_ROOT actually is.

It sounds a lot like an environment variable, but I don't know what I should set it to, since I'm using a ClasspathResourceLoader, and the file to be included is not an actual file located in a folder, it's inside the JAR containing the templates and the Java class (and all in the same package).

I found TEMPLATE_ROOT mentioned in another question, Where should I put Velocity template files for a command line utility built with Maven? , but it's related to using a FileResourceLoader. I need to keep using the ClasspathResourceLoader and I need all the files to be in the JAR, not outside it as normal files in some folder.

TestVelocity.java

package testpackage;

import java.io.StringWriter;
import java.util.Properties;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

public class TestVelocity
{
  public static String getText()
  {
    String text = "";

    Properties properties = new Properties();

    properties.setProperty("resource.loader", "class");

    properties.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

    VelocityEngine engine = new VelocityEngine();

    VelocityContext context = new VelocityContext();

    StringWriter writer = new StringWriter();

    try
    {
      engine.init(properties);

      // This works because the template doesn't contain any #include
      Template template = engine.getTemplate("testpackage/TemplateWithoutInclude.vm");

      // This causes ResourceNotFoundException: Unable to find resource 'TemplateWithInclude.vm'
      // Template template = engine.getTemplate("testpackage/TemplateWithInclude.vm");

      template.merge(context, writer);

      text = writer.toString();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    return text;
  }
}

TemplateWithoutInclude.vm

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <p>Hello</p>
    </body>
</html>

TemplateWithInclude.vm

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        #include("MyInclude.vm")
    </body>
</html>

MyInclude.vm

<p>
    Hello
</p>

Answer

SantiBailors picture SantiBailors · Feb 14, 2014

Re the sample code, the problem is solved by adding one more property to the Properties instance used to initialize the engine:

properties.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, IncludeRelativePath.class.getName());

This allows to reference the path of the file to be included as a path relative to the folder where the including template is. So if both files are in the same folder then no path needs to be specified in the #include directive: just #include("MyInclude.vm").

I was also hoping to learn something about the obscure (to me) TEMPLATE_ROOT, like for ex. what it is, since it is surprisingly difficult for me to find this info documented anywhere. But whatever it is, at least in my case it corresponds to the root package of the Java project (the "default" package). This means that if I don't use the additional property mentioned above, then placing the file MyInclude.vm in the root package works.