Skip to main content

JSF Tip #40 - Loading resource library contracts from the filesystem

Posted by mriem on November 15, 2013 at 8:00 AM PST

On the #jsf IRC channel Ryan asked how you can get it so the JSF runtime loads resource library contracts from the filesystem. This blog entry will show you how.

BE AWARE this code is a proof of concept. To make it production ready make sure you sanitize incoming resource URLs thoroughly!

The FilesystemResourceHandler

public class FilesystemResourceHandler extends ResourceHandlerWrapper {
       
    private final File resourceDirectory;
   
    private final ResourceHandler resourceHandler;

    public FilesystemResourceHandler(ResourceHandler resourceHandler) {
        this.resourceHandler = resourceHandler;
        this.resourceDirectory = new File("/tmp/contracts");
    }

    @Override
    public ResourceHandler getWrapped() {
        return resourceHandler;
    }

    @Override
    public Resource createResource(String resourceName, String libraryName) {
       
        List activeContracts = FacesContext.getCurrentInstance().getResourceLibraryContracts();

        if (!resourceName.startsWith("filesystemResourceHandler")) {
            if (activeContracts != null) {
                for(File file : this.resourceDirectory.listFiles()) {
                    for (String contract : activeContracts) {
                        if (contract.equals(file.getName())) {
                            if (libraryName != null) {
                                return new FilesystemResource(resourceDirectory, contract, libraryName, resourceName);
                            } else {
                                return new FilesystemResource(resourceDirectory, contract, null, resourceName);                   
                            }
                        }
                    }
                }
            }
        } else {
            resourceName = resourceName.substring(resourceName.indexOf("/") + 1);
            String contract = resourceName.substring(0, resourceName.indexOf("/"));
            resourceName = resourceName.substring(resourceName.indexOf("/") + 1);
            return new FilesystemResource(resourceDirectory, contract, null, resourceName);
        }
       
        return resourceHandler.createResource(resourceName, libraryName);
    }

    @Override
    public boolean isResourceURL(String url) {
        if (url.contains("filesystemResourceHandler")) {
            return true;
        }
        return resourceHandler.isResourceURL(url);
    }
}

The FilesystemResource

public class FilesystemResource extends Resource {

    private final File baseDir;
   
    private final String contract;
   
    public FilesystemResource(File baseDir, String contract, String libraryName, String resourceName) {
        this.baseDir = baseDir;
        this.contract = contract;
        setLibraryName(libraryName);
        setResourceName(resourceName);
    }

    @Override
    public InputStream getInputStream() throws IOException {
        /*
         * Be aware you need to make sure you clean libraryName and resourceName here,
         * this code is merely a proof of concept!
         */
        File resourceFile = new File(baseDir,
            contract + "/" +
            (getLibraryName() != null ? getLibraryName() + "/" : "") +
            getResourceName());
        return new FileInputStream(resourceFile);
    }

    @Override
    public Map getResponseHeaders() {
        return Collections.EMPTY_MAP;
    }

    @Override
    public String getRequestPath() {
        /*
         * Note this URL should take prefix mapping or extension mapping into account,
         * right now assuming you have mapped /faces/*
         */
        return FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() +
                "/faces/javax.faces.resource/filesystemResourceHandler/" +
                contract + "/" +
                (getLibraryName() != null ? getLibraryName() + "/" : "") +
                getResourceName();
    }

    @Override
    public URL getURL() {
        return null;
    }

    @Override
    public boolean userAgentNeedsUpdate(FacesContext context) {
        return true;
    }
}

The faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
    <application>
        <resource-handler>filesystemrlc.FilesystemResourceHandler</resource-handler>
    </application>
   
</faces-config>

Note the code for this sample is available as part of the Glassfish samples (see the subversion repository at https://svn.java.net/svn/glassfish-samples~svn/trunk/ws/javaee7/jsf/filesystemrlc/)

And that is it.

Enjoy!

Related Topics >>