简体   繁体   中英

Velocity in OSGi: how to load templates from classpath

I am developing an application for OSGi with velocity template engine. It works great for loading my templates by file loader but now I have to implement this templates in my jar and load it as resources.

How can i made it work?

My Code:

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

ve.getTemplate("foo.vm");

This will throw an exception like

Unable to find resource 'index.vm'

Caused by:

org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'index.vm'

Sadly Velocity is not that OSGi friendly. Therefore you cannot use the built in ClasspathResourceLoader and it is hard to add a custom developed ResourceLoader as well.

I suggest that you should get your template as a Reader in any of the ordinary ways and choose one of the following:

  • Use the VelocityEngine evaulate function if you need to merge the template rarely (performance does not matter that much)
  • Create a Template by hand with the inner classes of Velocity

The first option can be used if you do not have to merge your templates very often so performance is not a key requirement.

Here is a sample for the second option where the created template object can be reused by calling the merge function on it (expecting that you already got a Reader to your vm file or resource):

RuntimeInstance runtimeInstance = new RuntimeInstance();
runtimeInstance.init();
SimpleNode simpleNode = runtimeInstance.parse(reader, "nameOfYourTemplateResource");

Template template = new Template();
simpleNode.init(new InternalContextAdapterImpl(new VelocityContext()), runtimeInstance);
template.setData(simpleNode);

template.merge(...);
...

To get a reader for the vm file in OSGi you should choose a class that is surely in the same bundle as your vm resource and call SameBundleClass.class.getResourceAsStream... You can transform your stream to writer with InputStreamReader than.

Please note that the example misses some try-catch-finally block.

Two things to verify

1. Classpath issues

Make sure you set the classpath of the OSGi bundle via the MANIFEST.MF to include a dot:

Bundle-ClassPath: .

The dot means to include the root of the bundle in the class-loading hierarchy, where your folder "velocitytemplates" likely resides.

And you need to have the Velocity jar-files in the same bundle where your template-files reside, because otherwise you'll get classloading issues as Velocity would reside in a different bundle and thus would not see the "velocitytemplates" at all in its classpath.

2. There is no "path" for ClasspathResourceLoader

ClasspathResourceLoader does not support setting a "path", as it uses the Classpath by definition, so either add "velocitytemplates" to the Classpath in the OSGi bundle (MANIFESt.MF) or reference the velocity templates with complete path, ie "velocitytemplates/index.vm"

After 2 days I and a colleague have found the solution Velocity Engine have for default: file.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader

Created own class Resource loader like that

public static final class PdfResourceLoader extends ResourceLoader

@Override
public void init(ExtendedProperties configuration)
{
}

@Override
public InputStream getResourceStream(String source) throws ResourceNotFoundException
{
  return getClass().getResourceAsStream(source);
}

@Override
public boolean isSourceModified(Resource resource)
{
  return false;
}

@Override
public long getLastModified(Resource resource)
{
  return 0;
}
}

set the new context class loader

Thread.currentThread().setContextClassLoader(PdfResourceLoader.class.getClassLoader());  
    VelocityEngine ve = new VelocityEngine();

changed the property for default inside the velocity engine

ve.setProperty("resource.loader", "pdf"); 
ve.setProperty("pdf.resource.loader.class",
PdfResourceLoader.class.getName());
ve.init(); 

Example name path template

String pathTemplate = "/templates/yourTemplateName.html";      
Template t = ve.getTemplate(pathTemplate, "UTF-8"); 

That it's

I encountered a similar problem with class loader based templates where I wanted to specify a different root. I worked around it by subclassing ClasspathResourceLoader.

package my.package;

import java.io.InputStream;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

public class PrefixedClasspathResourceLoader 
    extends ClasspathResourceLoader
{
    /** Prefix to be added to any names */
    private String prefix = "";

    @Override
    public void init(ExtendedProperties configuration) {
        prefix = configuration.getString("prefix","");
    }

    @Override
    public InputStream getResourceStream(String name)
            throws ResourceNotFoundException 
    {
        return super.getResourceStream(prefix+name);
    }
}

With the following properties set

resource.loader=myloader
myloader.resource.loader.class=my.package.PrefixedClasspathResourceLoader
myloader.resource.loader.prefix=/velocitytemplates/

this way if you've got a template called "index.vm", velocity will use the classloader to find a resource called "/velocitytemplates/index.vm"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM