52

I have a project where I want to load a velocity template to complete it with parameters. The whole application is packaged as a jar file. What I initially thought of doing was this:

VelocityEngine ve = new VelocityEngine();

   URL url = this.getClass().getResource("/templates/");

   File file = new File(url.getFile());

   ve = new VelocityEngine();
   ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
   ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, file.getAbsolutePath());
   ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");

   ve.init();

   VelocityContext context = new VelocityContext();

   if (properties != null) {
    stringfyNulls(properties);
    for (Map.Entry<String, Object> property : properties.entrySet()) {
     context.put(property.getKey(), property.getValue());
    }
   }

   final String templatePath = templateName + ".vm";
   Template template = ve.getTemplate(templatePath, "UTF-8");
   String outFileName = File.createTempFile("report", ".html").getAbsolutePath();
   BufferedWriter writer = new BufferedWriter(new FileWriter(new File(outFileName)));

   template.merge(context, writer);

   writer.flush();
   writer.close();

And this works fine when I run it in eclipse. However, once I package the program and try to run it using the command line I get an error because the file could not be found.

I imagine the problem is in this line:

ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, file.getAbsolutePath());

Because in a jar the absolute file does not exist, since it's inside a zip, but I couldn't yet find a better way to do it.

Anyone has any ideas?

Rafael
  • 2,373
  • 4
  • 22
  • 28
  • 3
    for the love of god, highlight the code in your post and click the code button so that its formatted properly! :) – vicatcu May 28 '10 at 18:28
  • 1
    Are you quite certain that the /templates/ directory is getting exported into your jar? You have to mark it as part of your build settings and stuff. – vicatcu May 28 '10 at 18:29
  • didn't known about the code button, thanks. Yes, the templates dir is getting into the jar file. – Rafael May 28 '10 at 18:35

5 Answers5

77

If you want to use resources from classpath, you should use resource loader for classpath:

ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); 
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
Sathiamoorthy
  • 8,831
  • 9
  • 65
  • 77
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • I will test it! do you know where I can find documentation to each RuntimeContants and its possible values? – Rafael May 28 '10 at 20:48
  • Couldn't make it work, it doesn't find the file in neither eclipse nor in the jar. – Rafael May 28 '10 at 20:56
  • @Rafael: Fixed. `ClasspathResourceLoader` is not registered by default. – axtavt May 28 '10 at 21:28
  • Thanks, it did work when I used this along with the idea presented by ZZ Coder below. – Rafael May 31 '10 at 13:41
  • Works as written with v 1.7. Thanks. ` VelocityEngine ve = new VelocityEngine(); ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); ve.init(); ` – David Williams Sep 22 '13 at 17:22
  • Also look at ve.evaluate(..) which allows a java.io.Reader to be passed – John Deverall Mar 04 '15 at 15:10
  • 1
    It is unfortunate that [Javadocs of ClasspathResourceLoader](https://velocity.apache.org/engine/devel/apidocs/org/apache/velocity/runtime/resource/loader/ClasspathResourceLoader.html) wrongly says that resource loader value should be "class" - I tried that and it did not work, only to discover later that it should be "classpath" as mentioned in this answer – Wand Maker Apr 18 '16 at 09:23
24

Final code, developed using the ideas presented in both answers above:

VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());

ve.init();

final String templatePath = "templates/" + templateName + ".vm";
InputStream input = this.getClass().getClassLoader().getResourceAsStream(templatePath);
if (input == null) {
    throw new IOException("Template file doesn't exist");
}

InputStreamReader reader = new InputStreamReader(input);

VelocityContext context = new VelocityContext();

if (properties != null) {
    stringfyNulls(properties);
    for (Map.Entry<String, Object> property : properties.entrySet()) {
        context.put(property.getKey(), property.getValue());
    }
}

Template template = ve.getTemplate(templatePath, "UTF-8");
String outFileName = File.createTempFile("report", ".html").getAbsolutePath();
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(outFileName)));

if (!ve.evaluate(context, writer, templatePath, reader)) {
    throw new Exception("Failed to convert the template into html.");
}

template.merge(context, writer);

writer.flush();
writer.close();
Brad Turek
  • 2,472
  • 3
  • 30
  • 56
Rafael
  • 2,373
  • 4
  • 22
  • 28
  • Thank you, this helped me a great deal! Might be that my adapation works just a bit different, but using VelocityEngine#evaluate(...), I could leave out the instantiation of the Template and the call to Template#merge(...) – Argelbargel Jun 23 '11 at 23:56
  • works like a charm. The RuntimeInstance solved my problem allowing me to load the template in my bundle. Best solution for OSGI – iberbeu Dec 11 '14 at 09:25
  • This solution worked well, except I did not need the merge part at the end. – Mr. Polywhirl Dec 13 '19 at 13:06
10

Unless JAR is exploded, you can't read the resource in JAR as file. Use an input stream.

See following code snippets,

    InputStream input = classLoader.getResourceAsStream(fileName);
    if (input == null) {
        throw new ConfigurationException("Template file " +
                fileName + " doesn't exist");           
    }

    InputStreamReader reader = new InputStreamReader(input);            
        Writer writer = null;

        try {
            writer = new OutputStreamWriter(output);        

            // Merge template
            if (!engine.evaluate(context, writer, fileName, reader)) 
                ......
ZZ Coder
  • 74,484
  • 29
  • 137
  • 169
2

To make Velocity look for the templates in classpath:

VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class",ClasspathResourceLoader.class.getName());
ve.init();
0

Maybe I have an old version, this is the only thing that worked for me

ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "class"); 
ve.setProperty("classpath.resource.loader.class", 
ClasspathResourceLoader.class.getName());
Ignacio Tomas Crespo
  • 3,401
  • 1
  • 20
  • 13