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

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);
        }

        ...
        

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>
        

Posted November 15, 2013

Up