Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Freemarker with Restlet 2.0 in a Java EE server

I'm a bit confused with what is written in the documentation(s) for Freemarker and Restlet's freemarker extension.

Here's the situation: The restlet engine serves an HTML representation of a resource (e.g. www.mysite.com/{user}/updates). The resource returned for this URI is an HTML page containing all the updates, that is created with a freemarker template. This application is hosted on a Glassfish v3 server

Question(s):

  • The freemarker configuration should only be loaded once as per the freemarker documentation:

        /* You should do this ONLY ONCE in the whole application life-cycle:Create and adjust the configuration */
        Configuration cfg = new Configuration();
        cfg.setDirectoryForTemplateLoading(
                new File("/where/you/store/templates"));
        cfg.setObjectWrapper(new DefaultObjectWrapper());
    

    What is the best place to do this in a Java EE app? I am thinking of having it as context-param in web.xml and using a ServletContextListener - but I'm not sure how to go about doing that.

  • As per freemarker's documentation we could also add a freemarkerservlet and map .ftl url-patterns to it. But this is already mapped by a Restlet servlet (i.e., the url-pattern of "/"). So having another one for *.ftl doesn't make sense (or does it?)

So the question is basically about how best to integrate with the 'configuration' of Freemarker so that it happens only once and what is the 'entry-point' for that piece of code (who calls it). Has anyone successfully used Freemarker + restlet in a Java EE environment? Any ideas?

Thanks!

like image 366
PhD Avatar asked Mar 21 '11 03:03

PhD


1 Answers

This was a tricky question - indeed. Required me to go through the implementation of the source files in org.restlet.ext.Freemarker package - Phew!

Here's how you can do it

  1. If you need to create your OWN Configuration Object, set the 'templateLoader' to use and then have TemplateRepresentation 'work' on it for rendering:

    Configuration cfg = new Configuration();
    
    ContextTemplateLoader loader = new ContextTemplateLoader(getContext(),"war:///WEB-INF");
    
    cfg.setTemplateLoader(loader);
    
    TemplateRepresentation rep = null;
    
    Mail mail = new Mail(); //The data object you wish to populate - example from Restlet itself
        mail.setStatus("received");
        mail.setSubject("Message to self");
        mail.setContent("Doh!");
        mail.setAccountRef(new Reference(getReference(), "..").getTargetRef()
                .toString());
    
      Map<String, Object> data = new HashMap<String, Object>();
      data.put("status", mail.getStatus());
      data.put("subject", mail.getSubject());
      data.put("content", mail.getContent());
      data.put("accountRef", mail.getAccountRef());
    
      rep = new TemplateRepresentation("Mail.ftl", cfg, data, MediaType.TEXT_HTML);
    
      return rep;
    
  2. If you are happy with the default and wish to use a class loader based way of loading the templates

    //Load the FreeMarker template
        Representation mailFtl = new ClientResource(
                LocalReference.createClapReference(getClass().getPackage())
                        + "/Mail.ftl").get(); 
      //Wraps the bean with a FreeMarker representation
    return new TemplateRepresentation(mailFtl, mail, MediaType.TEXT_HTML);
    
  3. If you want to initialize the Configuration Object once and set the template by calling the setServletContextForTemplateLoading(...) method on the configuration object. You could always do this in a ServletContextListener


public class Config implements ServletContextListener {
    private static Configuration cfg = new Configuration();

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        cfg.setServletContextForTemplateLoading(sc, "/WEB-INF");        
    }
    public static Configuration getFMConfig()
    {
        return cfg;
    }
}

And then call the static getFMConfig() and pass it to TemplateRepresentation as in 1

Things to note:

  • If you do get a protocol not supported Exception it'll be in case 2. Implies that the ServerResource doesn't know what protocol to use to access the file - It'll be the CLAP protocol of Restlet. You may have to set up the init-params for RestletServlet in the web.xml file and have CLAP as one of the param-values
  • The TemplateRepresentation has quite a few constructors - if you DON'T pass in a configuration object during instantiation (using another overloaded constructor), it will create a new Configuration() for you. So you don't have to do any configuration set up as in 2 (This may strike you as obvious but I assumed that you WOULD still need to set a configuration or it would 'pick it up from somewhere')
  • If you DO wish to have your OWN configuration setup you MUST pass it to one of the constructors
  • Have a look at the "war:///" string in the constructor of ContextTemplateLoader in 1. this is important No where is it mentioned what this baseUri reference should be, not even in the docs. I tried for quite a while before figuring it out that it should be "war:///" followed by the folder name where the templates are stored.
  • For case 2 you'll probably have to store the templates in the same package as the class file from where this code is accessed. If you see carefully you'll notice a LocalReference parameter as an argument to ClientResource saying that the resource is supposed to be locally present and thus you need to use the custom CLAP protocol (classLoader Access Protocol)

Personal Frustration point - why isn't all this even clarified in the documentation or ANYWHERE :)

Hope it helps someone who stumbles upon this post! Phew!

like image 142
PhD Avatar answered Oct 05 '22 13:10

PhD