Accessing Static and Dynamic Resources

Mark Basler
Status: In Early Access

Problem Description

When designing components, developers will find that common tasks will need to be repeated for most development projects.  One function that is commonly required is accessing static and/or dynamic resources.  Static resources are usually Javascript files, Cascading Style Sheet (CSS) files, images, presentation fragments and so on.  Dynamic resources are usually associated with functions that change based on the input, like AJAX calls, form submissions and link actions.  This entry explores the different approaches available in fulfilling static & dynamic resource requests.

Solution

Depending on the mode of distribution that your component uses, different methods of accessing resources are available.  If the distribution is a Web Archive (WAR) or an Enterprise Archive (EAR) then you have a full array of options of how to access you resources, whether static or dynamic.  You can even have the resources packaged in your archive so they can be accessed directly from the web page.

Most component distributions involve packaging all related resources in a Java Archive (JAR) that would accompany the application for which they are used.  This type of packaging can limit the options that are available to access resources due to the fact that the component developer would want to minimize the amount of configuration necessary for the component user.  In this case, having the resource load using the direct access method would expose the component user to some unnecessary configuration, like copying resource files to a specific location in their application distribution or register artifacts in the application's deployment descriptors.

Below we discuss the different methods that can be used to access static and dynamic resources.

Direct Access

The direct access method involves packaging the resources with the archived distribution.  This is more of the traditional web design, where images, Javascript files and CSS files are packaged under the web module, accessible via URL and served directly by the Web or Application server.  In some cases the component resources will be accessible through a servlet, this would require the component user to define the servlet/servlet-mapping in the web application standard deployment descriptor. 

This is a common paradigm to access resources for web applications.  But it does require the developer to perform additional configuration to use the component.  There is also a risk that resource names may not be unique across components, which might lead to cases where you cannot combine those components in the same page.  This would only be appropriate if the component is not going to be utilized by other applications or a small group of developers will be using the component.

Renderer

The component's renderer can be used to serve resources that are accessed through the FacesServlet.  During the "Apply Request Values" phase the renderer takes control and can perform the task of supplying the necessary static resources or delegate/perform the appropriate functionality in a dynamic call.  Once the renderer has finished its task, the responseComplete method would be called on the FacesContext to short circuit the rest of the JSF Lifecycle.

This approach has some serious consequences in terms of performance and side effects.  Before the "Apply Request Values" phase the "Restore View" phase has to reconstitute the component tree.  This can be time consuming and impact performance, especially if state is maintained on the client.  When using the responseComplete method, the phase has to finish executing, which could cause undesirable side-effects to the component tree.  Side-effects can also occur when components have their "immediate" attribute set to true, which would cause the component's logic (validation, conversion & events) to be executed before the "Apply Request Values" phase ends.  Given these limitations, this is not the optimal approach.

PhaseListener

A PhaseListener can be registered so that during the "Restore View" phase, the request can be handled by the PhaseListener, possibly delegating tasks to a managed bean through a JSF 1.2 deferred method (this replaced the JSF 1.1 method binding approach).   For a static request, the PhaseListener would locate the resource by using some identifying mechanism like part of the Request URL that is available through the passed in phaseEvent.getFacesContext().getViewRoot().getViewId() or extract a value from the Map of the HttpServletRequest's parameters that can be located through the PhaseEvent argument with the call  phasesEvent.getFacesContext().getExternalContext().getRequestParameterMap().

The main problem with coding a PhaseListener for each component's specific needs is that the PhaseListeners in all the jars that are registered in all the faces-config.xml files bundled with your application are fired sequentially, with no guarantee to the order.  We actually ran into a problem where resource requests were served multiple times, which caused the response to be populated with multiple copies of the resource file, placed end-to-end.  The problem frequently occurs when you have multiple developers coding components in different ways.  You will find that even in the strictest development environments, potential conflicts could arise.  Also, the very existence of multiple PhaseListeners creates a performance burden since all that are registered execute on every request.

Third Party Libraries

Since it is hard to keep developers from adding functionality to existing PhaseListeners.  The best approach for accessing static and dynamic resources would be not to have custom PhaseListeners at all.  For this reason we satisfy all our static/dynamic requests using Shale-Remoting libraries.  Shale-Remoting uses a single PhaseListerner that doesn't require developer configuration to fulfill static requests and will delegate dynamic requests to a deferred method of a managed bean.  If the components in the librarys use this methodology then all the static/dynamic requests can be satisfied without developing custom code to propagate the request.   Also, the Shale-Remoting approach uses a URL that starts at the web context root, so the page designer doesn't have to keep track of the pages location within the web application to make sure it goes through the FacesServlet.

Static Example:

This static example snippet was taken from the com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer class and slightly altered to facilitate clarity.  In planning our component packaging, we decided to store our resources under the component's name in the jar's /META-INF directory.  For example, the FileUpload's Javascript file is stored at  "/META-INF/fileupload/fileupload.js".  The snippet below shows how a Javascript file ("fileupload.js")  and a CSS file ("fileupload.css") static resources are accessed using the Shale-Remoting APIs

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;

/**
* <p>Stateless helper bean to manufacture resource linkages.</p>
*/
private static XhtmlHelper helper = new XhtmlHelper();


public void encodeEnd(FacesContext context, UIComponent component) throws IOException {

if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();

....

//shale remoting resource retrieval
helper.linkJavascript(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.js");
helper.linkStylesheet(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.css");

...
}

Dynamic Example:

This dynamic example snippet was taken from the com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer class and slightly altered to facilitate clarity.  The example below shows how Shale-Remoting is used to dynamically access the managed bean's ("bpui_fileupload_handler")  "handleFileUpload" method that is registered in the application's faces-config.xml file.  Once the dynamic call String in created, it is used as a parameter in the "onsubmit" Javascript event handler function that populates the XMLHttpRequest URL facilitated by the Dojo bind function.  Executing the dynamic Shale-Remoting call  through AJAX (XMLHttpRequest) delegates control to the managed bean's method to perform the appropriate functionality.  Then the org.apache.shale.remoting.faces.ResponseFactory is used to create a javax.faces.context.ResponseWriter so that elements can be written using the startElement/EndElement convenience methods to facilitate return of the appropriate response. 

FileUploadRenderer Snippet:

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;

private static XhtmlHelper helper=new XhtmlHelper();

public void encodeBegin(FacesContext context, UIComponent component) throws IOException {

if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();

....

// shale remoting callback for status
String fileUploadCallback = helper.mapResourceId(context, Mechanism.DYNAMIC_RESOURCE,
"/bpui_fileupload_handler/handleFileUpload");
outComp.getAttributes().put("onsubmit", "return bpui.fileupload.submitForm(this, '" + retMimeType + "', '" + retFunction + "','" +
progressBarDivId + "', '" + fileUploadCallback + "')");

...
}

Note: When using Shale-Remoting dynamic calls the response header is set to allow the browser to cache response data when the request URL hasn't changed.  This could cause problems in a component like the FileUpload ProgressBar, where the same URL is used to poll for status updates (especially in Internet Explorer).  Shale-Remoting is going to address this in a future release, but until that happens, you can tell the browser not to cache the response by setting response headers, which is depicted in the code snippet below taken from the com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler class' "handleFileStatus" method.  It may be that modern browsers do not need all of these headers, but  this method is used by Stuts and is planned to be used in Shale-Remoting. 
        FacesContext context=FacesContext.getCurrentInstance();
HttpServletResponse response=(HttpServletResponse)context.getExternalContext().getResponse();
response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);

faces-config.xml Snippet:

The managed beans configured below are used to link the request to the managed bean via Shale-Remoting.  Also, note that the "fileUploadStatus" managed bean is pre-populated for use in the bpui_fileuploag_handler managed bean.
    <managed-bean>
<managed-bean-name>bpui_fileupload_handler</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>fileUploadStatus</property-name>
<value>#{fileUploadStatus}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>fileUploadStatus</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadStatus</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

If you wanted to have the managed bean populated with a parameter from the HttpServerRequest, the managed-property declaration for a form element named formInputElement would look something like:
     <managed-property>
<property-name>formInputElementValue</property-name>
<value>#{param.
formInputElement}</value>
</managed-property>
More information on JSF's implicit objects is located in the JavaEE5 tutorial.  The HttpServerRequest parameter value could also be retrieved in the managed bean using the Map of the HttpServletRequest's parameters that can be located through the the call FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().

FileUploadHandler Snippet:

Once the Shale-Remoting libraries link the request to the managed bean, the ResponseFactory is used to create a ResponseWriter to facilitate the request's response
import org.apache.shale.remoting.faces.ResponseFactory;
import javax.faces.context.ResponseWriter;

private static ResponseFactory factory=new ResponseFactory();

public void handleFileUpload() {
FacesContext context=FacesContext.getCurrentInstance();

...

try {
ResponseWriter writer = factory.getResponseWriter(context, "text/xml");
writer.startElement("response", null);
writer.startElement("message", null);
writer.write(status.getMessage());
writer.endElement("message");
...

writer.endElement("response");
writer.flush();
} catch (IOException iox) {
getLogger().log(Level.SEVERE, "response.exeception", iox);
}
}

References


© Sun Microsystems 2006. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.