How to Load External Resources in JSF Like Resources Served from the Resources Directory

Electrical Plug
JSF2 has great, easy-to-use resource handling. Whether it’s from a Component, a Composite Component, Template or an Include, one can load a css file or javascript file from a jar file on demand.  All you need to do is use a simple Annotation or a little bit of markup to define the library and file.

But what if you want to handle external resources the same way? Unfortunately, JSF doesn’t allow you to handle the two kinds of resources consistently.

Recently, I was working on a Component that required jQuery. I prefer to serve jQuery from Google’s CDN, and since I couldn’t load it on demand, it had to be defined in the Template XHTML file. So, it was loaded on all pages regardless whether it was needed or not. Moreover, my custom Component would not have jQuery defined as a resource dependency, so if it ended up on a page that didn’t have jQuery loaded, the component, of course, would not work. This is clearly less than ideal.

So, I figured out a workaround to handle external resources in the same manner one does with internal resources. My solution is not perfect by any means and it could be improved quite a bit, but as it is, it works really well for its intended purpose.

To solve this problem I created 2 Beans:

  • ResourceHandler.java
  • ResourceMapping.java

And then I created 2 Components:

  • ExternalResource.java
  • InternalResource.java

A third Component named OutputExternalResource.java was created to load an external resource from an XHTML file. More on that later. For now we’ll focus on loading resources on demand from inside of a Component Class.

After the 4 Objects above were ready, all that remained was to modify my custom Component to add the resources to the View after the Component was added to the View.


Step 1 – Create ResourceMapping.java

ResourceMapping is a really simple class that doesn’t need much explanation. It names a resource, defines where it is located and defines what kind of resource it is. It also handles the rendering of the resource into its markup form (which should perhaps be in a different Class, but is fine here for now).

Source of ResourceMapping.java

package org.quinnandrews.faces.bean.resource;

/**
 * @author Quinn Andrews
 *         Date: 2/9/12
 *
 *         Defines the name of a resource, what kind of resource it is
 *         and where it is located. Handles the HTML rendering of the
 *         resource, as well.
 *
 *         Does not handle images. CSS and Javascript only.
 *
 *         Only of use to our custom components. JSF standard components
 *         and components from libraries like Richfaces will still pull
 *         their resources from the classpath.
 *
 */
public class ResourceMapping {

    private String name;
    private String location;
    private String media; // for css only, screen by default
    private ResourceMappingType type;

    public ResourceMapping(final String name, final String location, final ResourceMappingType type) {
        this.name = name;
        this.location = location;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(final String location) {
        this.location = location;
    }

    public String getMedia() {
        if (media == null) {
            media = "screen";
        }
        return media;
    }

    public void setMedia(final String media) {
        this.media = media;
    }

    public ResourceMappingType getType() {
        return type;
    }

    public void setType(final ResourceMappingType type) {
        this.type = type;
    }

    public enum ResourceMappingType {
        STYLESHEET, JAVASCRIPT
    }

    public String renderMarkup() {
        if (getType().equals(ResourceMappingType.JAVASCRIPT)) {
            return renderJavascriptMarkup();
        } else if (getType().equals(ResourceMappingType.STYLESHEET)) {
            return renderStylesheetMarkup();
        }
        return "";
    }

    private String renderJavascriptMarkup() {
        return new StringBuilder()
                .append("<script type=\"text/javascript\"")
                .append(" src=\"").append(getLocation()).append("\"")
                .append("></script>")
                .toString();
    }

    private String renderStylesheetMarkup() {
        return new StringBuilder()
                .append("<link type=\"text/css\" rel=\"stylesheet\"")
                .append(" media=\"").append(getMedia()).append("\"")
                .append(" href=\"").append(getLocation()).append("\"")
                .append("/>")
                .toString();
    }

}


Step 2 – Create ExternalResource.java

ExternalResource is a Faces Component that doesn’t really do much at all. All it does is write the markup from the associated ResourceMapping Object, which is passed in through the constructor.

Source of ExternalResource.java

package org.quinnandrews.faces.component.resource;

import org.quinnandrews.faces.bean.resource.ResourceMapping;

import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import java.io.IOException;

/**
 * @author Quinn Andrews
 *         Date: 2/9/12
 *
 *         A clever component that tricks JSF into adding a resource that
 *         is served externally and not from the classpath. Used with
 *         ResourceMapping and ResourceHandler.
 *
 */
@FacesComponent(value = "org.quinnandrews.faces.component.EXTERNAL_RESOURCE")
public class ExternalResource extends UIOutput {

    private ResourceMapping resourceMapping;

    public ExternalResource(final ResourceMapping resourceMapping) {
        getAttributes().put("name", resourceMapping.getName()); // I believe this line is not necessary
        this.resourceMapping = resourceMapping;
    }

    @Override
    public void encodeBegin(final FacesContext context) throws IOException {
        context.getResponseWriter().write(resourceMapping.renderMarkup());
    }

    @Override
    public void encodeEnd(final FacesContext context) throws IOException {
    }

}


Step 3 – Create InternalResource.java

InternalResource is another simple Faces Component. It is specifically used with ResourceHandler to load internal resources in situations where you have a component that depends on both external and internal resources. The reason for doing this is to insure that resources are loaded in the correct order.

InternalResource sets the name, target, and library of the resource, in addition to setting the correct RendererType. This is basically what <h:outputStylesheet/> and <h:outputScript/> do. In fact, I may have been able to use those components instead of InternalResource.

Source of InternalResource.java

package org.quinnandrews.faces.component.resource;

import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;

/**
 * @author Quinn Andrews
 *         Date: 2/10/12
 *
 *         A Component for loading an internal resource
 *         via ResourceHandler.
 *
 */
@FacesComponent(value = "org.quinnandrews.faces.component.INTERNAL_RESOURCE")
public class InternalResource extends UIOutput {

    public InternalResource(final String name, final String library, final String target, final ResourceType type) {
        getAttributes().put("name", name);
        getAttributes().put("library", library);
        getAttributes().put("target", target);
        if (type.equals(ResourceType.JAVASCRIPT)) {
            setRendererType("javax.faces.resource.Script");
        } else if (type.equals(ResourceType.STYLESHEET)) {
            setRendererType("javax.faces.resource.Stylesheet");
        }
    }

    public enum ResourceType {
        STYLESHEET, JAVASCRIPT
    }

}


Step 4 – Create ResourceHandler.java

The ResourceHandler is also pretty straightforward. It is a request scoped JSF Managed Bean. It has a static Map called resourceMappings. When the Class is loaded, a group of ResourceMappings are instantiated, defined and put to resourceMappings when the static initializer is called. No doubt this aspect could be much better and more configurable, especially if this Class were to be more distributable. However, it works great as it is for now.

During a request, defined resources that have been rendered to the View are put into another Map (I guess I didn’t need to use a Map for this, since a simple list would have sufficed. I should fix that.). This is done in order to prevent the same resource from being rendered and loaded twice.

A Component calls for an external resource to be loaded by calling the addExternalResource(final String name) method. This method checks the Map of request resources to see if this resource has already been added to the View. If the resource has not yet been added, the resource is added by creating an instance of ExternalResource, passing the relevant ResourceMapping object into its constructor, and then passing it to the addResource(final UIOutput component) method, which adds the instance of ExternalResource to the View via the current instance of FacesContext.

Now if you have a situation where a Component depends on both external and internal resources, then an addInternalResource(final String name, final String library, final String target, final InternalResource.ResourceType type) method is provided to add the internal resource in the same manner as the external resource. This is important because if you have a Component where you define internal resources with annotations and external resources the way I do here, the internal resources will be loaded first and the external resources second. This is not very good, especially if you are serving jQuery from and external source and jQuery plugins internally.

When an internal resource is added, ResourceHandler does not check to see if the resource has already been loaded, deferring that responsibility to JSF (I’m not sure I have tested this yet). It creates an instance of InternalResource, passing in the relevant arguments, and adds it to the View with the addResource(final UIOutput component) method.

Source of ResourceHandler.java

package org.quinnandrews.faces.bean.resource;

import org.quinnandrews.faces.component.resource.ExternalResource;
import org.quinnandrews.faces.component.resource.InternalResource;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Quinn Andrews
 *         Date: 2/9/12
 *
 *         Defines resources available to components and manages
 *         integration with JSF View Rendering.
 *
 *         Allows components to dynamically add their necessary
 *         resources to the page while servings those resources
 *         from a different server or application or cdn, etc.
 *
 *         Only of use to our custom components. JSF standard components
 *         and components from libraries like Richfaces will still pull
 *         their resources from the classpath.
 *
 *         This Class does contain methods for adding an internally
 *         served resource, which is convenient when your component
 *         requires both external and internal resources. This allows for
 *         the resources to be rendered in the correct order.
 *
 */
@ManagedBean(name = "resourceHandler")
@RequestScoped
public class ResourceHandler {

    public static final String JQUERY = "q-jquery.js";
    public static final String JQUERY_TOOLS = "q-jquery-tools.js";
    public static final String JQUERY_CAROUFREDSEL = "q-jquery-caroufredsel.js";
    public static final String JQUERY_MOUSEWHEEL = "q-jquery-mousewheel.js";
    public static final String JQUERY_TOUCHWIPE = "q-jquery-touchwipe.js";

    public static final String CSS_CAROUSEL = "carousel.css";

    private static final Map<String, ResourceMapping> resourceMappings = new HashMap<String, ResourceMapping>();

    private Map<String, ResourceMapping> requestedResourceMappings;

    static {
        // SCRIPTS
        addResourceMapping(JQUERY, "https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js", ResourceMapping.ResourceMappingType.JAVASCRIPT);
        addResourceMapping(JQUERY_TOOLS, "http://cdn.jquerytools.org/1.2.6/all/jquery.tools.min.js", ResourceMapping.ResourceMappingType.JAVASCRIPT);
        addResourceMapping(JQUERY_CAROUFREDSEL, "http://www.quinnandrews.org/javascript/jquery.carouFredSel-5.5.0-packed.js", ResourceMapping.ResourceMappingType.JAVASCRIPT);
        addResourceMapping(JQUERY_MOUSEWHEEL, "http://www.quinnandrews.org/javascript/jquery.mousewheel.min.js", ResourceMapping.ResourceMappingType.JAVASCRIPT);
        addResourceMapping(JQUERY_TOUCHWIPE, "http://www.quinnandrews.org/javascript/jquery.touchwipe.min.js", ResourceMapping.ResourceMappingType.JAVASCRIPT);
        // STYLESHEETS
        addResourceMapping(CSS_CAROUSEL, "http://www.quinnandrews.org/css/carousel.css", ResourceMapping.ResourceMappingType.STYLESHEET);
    }

    private static void addResourceMapping(final String name, final String location, final ResourceMapping.ResourceMappingType type) {
        resourceMappings.put(name, new ResourceMapping(name, location, type));
    }

    public ResourceHandler() {
    }

    public Map<String, ResourceMapping> getRequestedResourceMappings() {
        if (requestedResourceMappings == null) {
            requestedResourceMappings = new HashMap<String, ResourceMapping>();
        }
        return requestedResourceMappings;
    }

    public ResourceMapping getResourceMapping(final String name) {
        return resourceMappings.get(name);
    }

    public ResourceHandler addExternalResource(final String name) {
        if (!getRequestedResourceMappings().containsKey(name)) {
            // If the resource has not already been added during this request, add it via FacesContext...
            addResource(new ExternalResource(getResourceMapping(name)));
            getRequestedResourceMappings().put(name, getResourceMapping(name));
        }
        return this;
    }

    public ResourceHandler addInternalResource(final String name, final String library, final String target, final InternalResource.ResourceType type) {
        addResource(new InternalResource(name, library, target, type));
        return this;
    }

    private void addResource(final UIOutput component) {
        FacesContext.getCurrentInstance().getViewRoot().addComponentResource(FacesContext.getCurrentInstance(), component);
    }

}


Step 5 – Using with a Custom Component

Now let’s put it all together. First, in your custom Component add the following Annotation after the @FacesComponent Annotation:

@ListenerFor(systemEventClass=PostAddToViewEvent.class)

Next, add a processEvent(final ComponentSystemEvent event) method, fetch the ResourceHandler and add your resources to the View like this:

public void processEvent(final ComponentSystemEvent event) throws AbortProcessingException {
    if (event instanceof PostAddToViewEvent) {
        final FacesContext context = FacesContext.getCurrentInstance();
        final ResourceHandler resourceHandler = context.getApplication().evaluateExpressionGet(context, "#{resourceHandler}", ResourceHandler.class);
        resourceHandler.addExternalResource(ResourceHandler.JQUERY)
                .addExternalResource(ResourceHandler.JQUERY_CAROUFREDSEL)
                .addInternalResource("jquery.mousewheel.min.js", "javascript", "head", InternalResource.ResourceType.JAVASCRIPT)
                .addInternalResource("jquery.touchwipe.min.js", "javascript", "head", InternalResource.ResourceType.JAVASCRIPT)
                .addExternalResource(ResourceHandler.CSS_CAROUSEL);
    }
    super.processEvent(event);
}


Step 6 – Using with an XHTML File

So now we have a way to load external resources from within a Component, but what about loading an external resource from within a Composite Component, a Template or an Include? All you need is a Component that does exactly what the custom Component does, but this Component will output only one resource, and that resource will be determined by the value of the Component’s name attribute (which is used to lookup the resource in ResourceHandler). OutputExternalResource is one such Component.

Source of OutputExternalResource.java

package org.quinnandrews.faces.component.resource;

import org.quinnandrews.faces.bean.resource.ResourceHandler;

import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostAddToViewEvent;
import java.io.IOException;

/**
 * @author Quinn Andrews
 *         Date: 2/14/12
 *
 *         Use this component to request an external resource
 *         that is defined in ResourceHandler.
 *
 */
@FacesComponent(value = "org.quinnandrews.faces.component.OUTPUT_EXTERNAL_RESOURCE")
@ListenerFor(systemEventClass=PostAddToViewEvent.class)
public class OutputExternalResource extends UIOutput {

    public OutputExternalResource() {
    }

    @Override
    public void encodeBegin(final FacesContext context) throws IOException {
    }

    @Override
    public void encodeEnd(final FacesContext context) throws IOException {
    }

    @Override
    public void processEvent(final ComponentSystemEvent event) throws AbortProcessingException {
        if (event instanceof PostAddToViewEvent) {
            final FacesContext context = FacesContext.getCurrentInstance();
            final ResourceHandler resourceHandler = context.getApplication().evaluateExpressionGet(context, "#{resourceHandler}", ResourceHandler.class);
            resourceHandler.addExternalResource((String) getAttributes().get("name"));
        }
        super.processEvent(event);
    }

}

Once you have OutputExternalResource in place, all you need to do is define a tag for it in your taglib.xml file and then use it in your XHTML files like this:

<q:outputExternalResource name="q-jquery.js"/>


Conclusion

It really is a shame that JSF doesn’t have a way for you to manage external resources consistently with internal resources. It’s quite surprising too, since it seems like it would not be difficult to develop support for it. At some point I may dig deeper into how JSF handles resources to see if there is a method or group of methods that can be overridden to introduce support for external resources.

Richfaces has support for mapping resources, which can be served from an external source, with a properties file, as described here. I played around with it a little bit, but didn’t get it to work (I was working with Richfaces 4.1 at the time). I really like how with their solution you can intercept requests to serve resources built into JSF and Richfaces and redirect those requests to external or alternate resources. However, if it works by performing a redirect, then it is not ideal, since that will generate a subrequest into the application and you might be in a position where you are trying to eliminate as many of those subrequests as possible. Anyway, I will likely give the Richfaces solution another go at some point.

As I mentioned above, my approach to solving this problem is far from perfect and could be improved quite a bit. Unfortunately it can’t do anything about the resources served from within JSF, Richfaces or other libraries, and it also leaves open the possibility of loading the same resource twice, one copy from an external source, the other from an internal source. Plus, in its current state, it is not as configurable as it should be. However, it does the job reasonably well, is really easy to setup, and makes things much more manageable in some respects.

Leave a Reply

*