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.