Packaging Your Facelets in JARs

By , 17 July 2008

Packaging Your Facelets in JARs

Because JSF automatically finds faces-config.xml in your jar files, it provides a neat opportunity for building a simple jar-based module system for your webapps. All you need is a way to bundle your templates and resources right? Well, thanks to Facelets it is actually very easy.

Facelets lets you implement your own ResourceResolver which is used to find templates. The following simple implementation extends the default resolver to check the classpath for the required template.

Packaging Your Facelets in JARs
/**
 * This facelets resource resolver allows us to put facelet files in jars 
 * on the classpath, as well as the context root of the webapp.
 *
 * @author roger
 */
public class TemplateResolver extends DefaultResourceResolver 
        implements ResourceResolver {
    private Logger log = Logger.getLogger(getClass().getName());
    
    /** first check the context root, then the classpath */
    public URL resolveUrl(String path) {
        log.fine("Resolving URL " + path);
        URL url = super.resolveUrl(path);
        if (url == null) {
            
            /* classpath resources don't start with / */
            if (path.startsWith("/")) {
                path = path.substring(1);
            }
            url = Thread.currentThread().getContextClassLoader().
                    getResource(path);
        }
        return url;
    }
}

You enable this resolver by configuring your web.xml with:

<context-param>
  <param-name>facelets.RESOURCE_RESOLVER</param-name>
  <param-value>au.com.ninthavenue.webcore.application.TemplateResolver</param-value>
</context-param>

That only gets us half way there, because we cannot use the ResourceResolver to serve external css, image and javascript resources. In order to do that, you can use a servlet which you will have to map one way or another in your web.xml. At Sunburnt, we don't actually map the servlet, but forward to it from a filter if a resource isn't found on the context path. The servlet looks like this:

/**
 * This servlet fetches a static resource from the classpath. Access to
 * java class files is restricted.
 */
public class ClasspathServlet extends HttpServlet {
    private Logger log = Logger.getLogger(getClass().getName());
    
    /** default constructor */
    public ClasspathServlet() {}

    /** serve the file from the classpath */
    @Override
    public void doGet(HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException {
        
        /* if this servlet is not mapped to a path, use the request URI */
        String path = request.getPathInfo();
        if (path == null) {
            path = request.getRequestURI().substring(
                request.getContextPath().length());
        }
        
        /* failure conditions */
        if (path.endsWith(".class")) {
            response.sendError(403, path + " denied");
            return;
        }
        
        /* find the resource */
        log.fine("Looking for " + path + " on the classpath");
        URL resource = Thread.currentThread().getContextClassLoader().
                getResource(path.substring(1));
        if (resource == null) {
            response.sendError(404, path + " not found on classpath");
        } else {
            
            /* check modification date */
            URLConnection connection = resource.openConnection();
            long lastModified = connection.getLastModified();
            long ifModifiedSince = request.getDateHeader("If-Modified-Since");
            if (ifModifiedSince != -1 && lastModified <= ifModifiedSince) {
                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                return;
            }

            /* write to response */
            response.setContentType(getServletContext().getMimeType(path));
            OutputStream out = new BufferedOutputStream(
                    response.getOutputStream(), 512);
            InputStream in = new BufferedInputStream(
                    resource.openStream(), 512);
            try {
                int len;
                byte[] data = new byte[512];
                while ((len = in.read(data)) != -1) {
                    out.write(data, 0, len);
                }
            } finally {
                out.close();
                in.close();
                if (connection.getInputStream() != null) {
                    connection.getInputStream().close();
                }
            }
        }
    } /* doGet() */
}

Finally, you'll need to remember to build your jar with the resources included. We do this using maven by adding the following in the <build> section (after dependencies):

<build>
  <resources>
    <resource><directory>src/main/resources</directory></resource>
    <resource><directory>src/main/webapp</directory></resource>
  </resources>
</build>

That's the basic idea, I hope you followed along. There are a few things you should be aware of though:

  • Relative URLs will be resolved from the same source. i.e. You can't use a relative URL inside a jar to resolve a template on the filesystem. That's only really a problem if you want your taglib in the jar but the actual tag files on the filesystem.
  • This resolver may leak file handles if you are using facelets < 1.1.15 (due soon?). See Facelets Issue #278.

The JSR314 expert group have talked briefly about including something like this for JSF 2.0. I suspect it'll probably be possible in one way or another since it's the sort of thing you need for bundling Facelets-based components.

About Roger Keays

Packaging Your Facelets in JARs

Roger Keays is an artist, an engineer, and a student of life. He has no fixed address and has left footprints on 40-something different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, the proper use of semicolons, and finding good food.

Leave a Comment

Please visit https://rogerkeays.com/blog/packaging-your-facelets-in-jars to add your comments.

Comment posted by: Moesio Medeiros, 11 years ago

 Great! But how to get resources from jar if I don't use faces? 

I'm trying to implement that servlet and Thread.currentThread().getContextClassLoader.getResource for some resource inside a jar always returns me null

Comment posted by: , 13 years ago

Please note, I don't distribute Furnace anymore because it is now too closely tied to our system. The code should be good though and you're welcome to use it. You'll just have to do some cut and paste and messing with your config files.

Comment posted by: FkJ, 15 years ago

I had reRender problems with Tomcat 6  after packing templates in a JAR.

Here is the solution:

www.jboss.com/index.html

Comment posted by: , 15 years ago

Hi Jep Jep. Thanks for pointing that out. I've updated the example code to close the URL InputStream. It's updated in Furnace 1.4 also.

Comment posted by: jep jep, 15 years ago

Doesn't ClassPathServlet have exactly the same resource leak as facelets (i.e. long lastModified = resource.openConnection().getLastModified();)?

Comment posted by: , 15 years ago

You can, but you must use a context-relative URL, i.e. it must begin with a /. Facelets resolves all template-relative URLs (those not beginning with a /) from the same repository as the template they are referenced in.

Comment posted by: Jason Long, 15 years ago

I have been putting all of my templates under WEB-INF and gluing the pages together as follows:

<ui:composition template="/WEB-INF/template/layout/base.xhtml">
  <ui:define name="title">Issue List</ui:define>
  <ui:define name="content">
      <ui:include src="/WEB-INF/template/issue/list.xhtml" />
  </ui:define>
</ui:composition>

Now I really need to package these in jars as my application is getting much larger.

I tried the following

<ui:composition template="/WEB-INF/template/layout/base.xhtml">
  <ui:define name="title">Issue List</ui:define>
  <ui:define name="content">
      <ui:include src="com/octgsoftware/pipetracker/model/issue/list.xhtml" />
  </ui:define>
</ui:composition>

However this wants to treat this a relative url.

Can I put my templates in jars and then glue them together in files not in jars?

Is there something that I am missing?

Comment posted by: Rafael Ponte, 15 years ago

Hi Roger, great post!

I'd like to know what the best way to pass the reference of the managed bean for my facelets template and continue using it normally, something similar as:
<ui:composition ... template="/masterTemplate.xhtml">
    <ui:param name="defaultBean" value="#{myActualManagedBean}" />
</ui:composition>

Then, in the masterTemplate.xhtml i could do something as:
<h:panelGroup rendered="#{defaultBean.currentState}">
    ...
</h:panelGroup>

Well, using ui:param is it possible that?
Thanks.

Comment posted by: , 15 years ago

Hi Olivier,

It depends how you map the ClasspathServlet. E.g. if you map it to /resources then a request for /resources/myapp/styles/default.css would search the classpath for myapp/styles/default.css. We use a filter to see if the file is available on the context path before checking the classpath.

Comment posted by: Olivier, 15 years ago

hello,

great article but it s not clear to me how you did and use your filter to serve css or image?

 

best regards