Search code examples
jsfjsf-2resourcesexternalfacelets

Obtaining Facelets templates/files from an external filesystem or database


I am able to successfully get this to work with the template in my app:

<ui:decorate template="/WEB-INF/templates/mytemplate.xhtml">

I am also able to move template to /META-INF/templates/mytemplate.xhtml of a JAR and get this to work:

<ui:decorate template="/templates/mytemplate.xhtml">

I would actually like to put this file onto filesystem (or database for that matter). How can I achieve this? I found plenty of things related to com.sun.facelets.impl.DefaultResourceResolver, but I don't think that is actually related to override the serving of the template. It is not trying resolve a URL, it is simply trying to get the file somehow on the classpath.


Solution

  • If you're already on JSF 2.2, you can do this by providing a custom ResourceHandler wherein you return the desired view resource in createViewResource().

    public class FaceletsResourceHandler extends ResourceHandlerWrapper {
    
        private ResourceHandler wrapped;
    
        public FaceletsResourceHandler(ResourceHandler wrapped) {
            this.wrapped = wrapped;
        }
    
        @Override
        public ViewResource createViewResource(FacesContext context, final String name) {
            ViewResource resource = super.createViewResource(context, name);
    
            if (resource == null) {
                resource = new ViewResource() {
                    @Override
                    public URL getURL() {
                        try {
                            return new File("/some/base/path", name).toURI().toURL();
                        } catch (MalformedURLException e) {
                            throw new FacesException(e);
                        }
                    }
                };
            }
    
            return resource;
        }
    
        @Override
        public ResourceHandler getWrapped() {
            return wrapped;
        }
    
    }
    

    Which is registered in faces-config.xml as below:

    <application>
        <resource-handler>com.example.FaceletsResourceHandler</resource-handler>
    </application>
    

    Or if you're not on JSF 2.2 yet, then use ResourceResolver instead.

    public class FaceletsResourceResolver extends ResourceResolver {
    
        private ResourceResolver parent;
    
        public FaceletsResourceResolver(ResourceResolver parent) {
            this.parent = parent;
        }
    
        @Override
        public URL resolveUrl(String path) {
            URL url = parent.resolveUrl(path); // Resolves from WAR.
    
            if (url == null) {
                try {
                    url = new File("/some/base/path", path).toURI().toURL();
                } catch (MalformedURLException e) {
                    throw new FacesException(e);
                }
    
            }
    
            return url;
        }
    
    }
    

    Which is registered in web.xml as below:

    <context-param>
        <param-name>javax.faces.FACELETS_RESOURCE_RESOLVER</param-name>
        <param-value>com.example.FaceletsResourceResolver</param-value>
    </context-param>
    

    Regardless of the way, in order to provide the resource from the database, you'd either save/cache them on (temp) disk file system so you can provide the URL just via File, or invent a custom protocol such as db:// and provide a custom URLStreamHandlerFactory and URLStreamHandler implementation to perform the actual job of streaming from the DB. You can find a kickoff example here Registering and using a custom java.net.URL protocol.