JSF 2.0 New Feature Preview Series (Part 4) Resource Re-location

This is the sixth entry in the JSF 2.0 New Feature Preview Series. The last entry covered the new event system. For this entry, we'll cover resource re-location. The driving force behind this feature is to simplify development of pages. A page author shouldn't need to know what resources a particular component needs. The Tomahawk component set does this by having the user install a Filter that post-processes the response produced by JSF. This solution doesn't scale all that well as the entire response needs to be buffered, parsed, manipulated and then rendered out. Building off the new resource and event systems, we can avoid doing this and allow resources to be placed where they should be (e.g. stylesheet and script references within the head element).

For now, let's start with the page level mechanics and then drill down into how this works. The 2.0 EDR1 specification calls for four new tags.

  • h:head - this represents the head element of an HTML page
  • h:body - this represents the body element of an HTML page
  • h:outputScript - this represents an external javascript file reference
  • h:outputStylesheet - this represents an external stylesheet reference

So armed with these new tags, let's create a simple facelet:

<!DOCTYPE html      
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"      
           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head id="head">    
        <title>resourcereslocation</title>
    </h:head>
    <h:body id="body">    
        <h:form id="form">        
            <h:outputScript name="simple.js" target="#{param.location}"/>
            <h:outputText value="Hello"/>
            <h:outputStylesheet name="simple.css" target="#{param.location}"/>
        </h:form>
    </h:body>
</html>

The head and body tags are fairly straight forward as to their attributes. Their main use is for knowing where to output relocated resources. Now, notice the outputScript and outputStylesheet tags are within the form element. The name attribute is for the name of the resource to be found by the ResourceHandler (NOTE: there is a library attribute available as well). The target attribute specifies where the rendered content should appear. In the sample above, we're using an expression so that we can pass a request parameter to change the behavior easily.

So given all of this, if there is no location request parameter or target isn't defined, so the style sheet reference will be rendered in the head and the script reference will be rendered inline:

<html xmlns="http://www.w3.org/1999/xhtml">    
    <head>
        <title>resourcereslocation</title>        
        <link type="text/css" rel="stylesheet" href="/ctx/faces/javax.faces.resource/simple.css" />
    </head>
    <body>
        <form id="form" name="form" method="post" action="..." enctype="...">                    
            <script type="text/javascript" src="/ctx/faces/javax.faces.resource/simple.js"></script>
            hello        
        </form>
     </body>
</html>

Issue the same request, this time with a request parameter location=head:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>resourcereslocation</title>
        <link type="text/css" rel="stylesheet" href="/ctx/faces/javax.faces.resource/simple.css" />
        <script type="text/javascript" src="/ctx/faces/javax.faces.resource/simple.js"></script>
    </head>
    <body>
        <form id="form" name="form" method="post" action="..." enctype="...">
            hello
        </form>
    </body>
</html>

Then issue the request again this time with the request parameter value equal to body:

<html xmlns="http://www.w3.org/1999/xhtml">    
    <head>        
        <title>resourcereslocation</title>
           <link type="text/css" rel="stylesheet" href="/ctx/faces/javax.faces.resource/simple.css" />        
    </head>
    <body>       
        <form id="form" name="form" method="post" action="..." enctype="...">            
            hello        
        </form>
        <script type="text/javascript" src="/ctx/faces/javax.faces.resource/simple.js"></script>    
    </body>
</html>

Notice that the style sheet render ignores the target attribute and always renderes the link within the head - as it should!

Recall how I said that page authors shouldn't have to know what resources a component needs? Well, the above examples only get you so far. It would be tedious if using a third party component set would require you to use different h:outputScript or h:outputStylesheet references depending on which component was being used. So the 2.0 specification has added the @ResourceDependency annotation to allow component authors to declare the resource(s) the component will need. A simple example might look like:

@ResourceDependency(name="style.css",library="corp")
public class MastHead extends UIComponentBase {
        .
        .
        .  
}

a more complex component may look like:

@ResourceDependencies({
    @ResourceDependency(name="style.css",library="corp"),
    @ResourceDependency(name="menu.js",library="corp",target="head")
})
public class Menu extends UIComponentBase {
        .
        .
        .
}

When the page author uses these components, they'll not need to know anything about any of the stylesheets or scripts. The necessary dependencies will be rendered as needed without further action.

So how does this work under the covers? Glad you asked. Let's base the follow of the component example with the single @ResourceDependency annotation.
  1. The Facelets runtime will create the component
  2. The component will be added as a child to some other component. Before returning from the add() method, the component will be checked for @ResourceDependency annotations (either the singluar or plural version).
  3. The @ResourceDependency is found. A new UIOutput component instance is created.
  4. The ResourceHandler is queried for an appropriate Renderer based on the content type of the resource, which in this case is text/css, so the style sheet renderer will be set as the Rendere for this new UIOutput.
  5. The values of the name, library, and target attributes (library and target are optional) from the annotation are stored in the component's attribute map.
  6. UIViewRoot.addComponentResource() is called passing in the UIOutput and the value of target attribute from the annotation (if any)
All of this has occurred while building the view. Now when we render the view, the head renderer (it will be the head renderer in this case as the example we're using is a style sheet) we simply encode each of the resources that have been targeted for the head like so:

UIViewRoot viewRoot = context.getViewRoot();
    for (UIComponent resource : viewRoot.getComponentResources(context, "head")) {
    resource.encodeAll(context);
}

Now I mentioned that the event system comes into play with the implementation of this feature. So I would be remiss if I didn't touch on that, but it requires going back to the example where the h:outputScript or h:outputStylesheet are referenced within the view. In this case, there is no component with annotations so when the component is added to the view, the sequence of events described above will not occur and thus UIViewRoot.addComponentResource() will not have been called. For this case, the style sheet and script renderers have a little extra magic to help obtain the same end result.

The renderers leverage another new annotation, @ListenerFor. This annotation allows a component to subscribe to particular events with the component itself, in this case the Renderer, as the listener. Since these two renderers will be listeners for these events, they also implement the ComponentSystemEventListener interface that was mentioned in the previous entry. So the Renderer with these new bells and whistles looks a little like:

@ListenerFor(systemEventClass=AfterAddToParentEvent.class, sourceClass=UIOutput.class)
public class ScriptRenderer extends Renderer implements ComponentSystemEventListener {
    .
    .
    .
    public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
        UIComponent component = event.getComponent();
        FacesContext context = FacesContext.getCurrentInstance();
        String target = (String) component.getAttributes().get("target");
        if (target != null) {
            context.getViewRoot().addComponentResource(context, component, target);
        }
    }
    .
    .
    .
}
When the Facelets runtime creates the component associated with the h:outputScript, it will obtain the Renderer for this component and interrogate it for any @ListenerFor annotations (there is a plural form of course, @ListenersFor). For each annotation found, the Renderer will be added as a component listener for, in the case above, the AfterAddToParent event. So when the Facelets runtime adds the component to the tree, the AfterAddToParent event is invoked and this Renderer's processEvent() method is invoked which will add the component as a resource to the ViewRoot with the appropriate target.

Well, I think that about covers it.  I had initially thought this blog was going to be quick and easy, but it kinda snowballed it seems. I hope this all made sense.   If not, please leave comments with questions and I'll do my best to clarify.
Comments:

It is a great entry.

I hope we are also planning to address the standardization of skinning feature.
This is very important, as at the end of the day clients want good looking screens.

Rich Faces skin cannot be applied to woodstock.Wood stock comes only with one skin.

Skinning interoperability is very critical.

Posted by Rahul Mahajan on June 26, 2008 at 03:34 AM PDT #

This is excellent. You mentioned:

<i>The ResourceHandler is queried for an appropriate Renderer based on the content type of the resource, which in this case is text/css, so the style sheet renderer will be set as the Rendere for this new UIOutput.</i>

I've always wanted a component set that renders a beautiful full featured components for desktops, and much simpler components for mobile devices automatically. Will there be any kind of standardization on device detection so the appropriate CSS and component HTML/javascript can be output?

Posted by Ryan de Laplante on June 26, 2008 at 06:30 AM PDT #

[Trackback] We all know that Ryan Lubke is a top notch engineer, but did you also know he's a solid technical writer? Ryan has been posting plenty of really useful content on his blog about JSF 2.0, including the series on new features in JSF 2.0. This entry summ...

Posted by Ed Burns's Blog on June 26, 2008 at 07:00 AM PDT #

@Rahul: Skinning is on the list of things to look at after EZComponent and a few other bit items are tackled.

@Ryan: Standardized device detection isn't on the table at this point. However, given that the ResourceHandler is replaceable/decoratable, it would be possible to implement this yourself if the spec doesn't address it. Sounds like a nice side project.

Posted by Ryan Lubke on June 26, 2008 at 07:48 AM PDT #

If I create a web app for desktop and mobile users, I still need to have two separate templates for the screens and logic to output the right one? I get the impression that RenderKit doesn't help me there. RenderKit is only useful for creating components that can be told to render for desktop or render for mobile, etc.?

Posted by Ryan de Laplante on June 28, 2008 at 01:20 AM PDT #

I believe this type of situation is why there should be no template-based markup in the page. Everything should be a component so that multiple renderkits can be used properly.

Posted by guest on June 28, 2008 at 02:10 AM PDT #

Oh, like using layout managers instead of HTML markup, etc. Great idea, thanks.

Posted by Ryan de Laplante on June 28, 2008 at 02:12 AM PDT #

Hi
please ensure that JSF 2.0 will be less eager of http session space, which can became a limitation in adpting this technologies for big enterprise projects. Also please explain to Sun developers that the getResource method and the Local handling should be somehow cached: searching for resources (like JSF and Struts do) in many hundred MB classpath scattered in many Jars can be a performance overkill (play around with JAX-WS and the oversynchronized Sun/Apache Catalog class and you'll see the same).
Have a nice day

Posted by Carlo on July 01, 2008 at 04:55 PM PDT #

While working with JSF+JSP, and JSF+Facelets I'm always wondering why JSF insists on making the template files accessible to the web. For example, if I try to access localhost:8080/edit.xhtml I can see the raw unprocessed facelets template. If I access localhost:8080/edit.jsf I see the actual page. I have JSP templates that give huge stack dump errors in server log when you try to access them directly, and only work when you do /faces/jspfilename.jsp Can someone explain why this was done, and maybe make the spec hide the template file from end users?

Posted by Ryan D on July 01, 2008 at 10:51 PM PDT #

@Carlo: With respect to resource path caching, it's there as an implementation detail. As far as session/memory usage, I'm guessing you're referring to the amount of space used to store the tree structure and state. There are options to tune this in the 1.2 releases of Mojarra and MyFaces. The 2.0 EG hopes to look into improving state management so expect to see changes.

@Ryan D: You can use security constraints defined in the web.xml to restrict direct access to the JSP without going through the FacesServlet. See this web.xml [1] in the guessNumber demo as an example.

[1] http://tinyurl.com/3f8tcv

Posted by Ryan Lubke on July 02, 2008 at 06:19 AM PDT #

Apparently some people are very upset about this feature. I just read a review about JSF 2.0 (http://jdevelopment.nl/java/jsf-20-a-glance/) where the following is being said about this feature:

"A component, possibly called by other components, could decide it wanted to change the head of a document. This is completely undesirable behavior. If the component is generic it should never bother with anything outside its scope."

Posted by christopher sharp on August 30, 2008 at 07:07 PM PDT #

[Trackback] TOTD #47 showed how to deploy a JSF 1.2 application (using Facelets and Ajax/JSF Extensions) on Mojarra 2.0-enabled GlassFish. &nbsp;In this blog we'll use new features added in JSF 2.0 to simplify our application: Use integrated Facelets and resource...

Posted by Arun Gupta's Blog on October 14, 2008 at 10:40 PM PDT #

We're currently using Weblets to serve static resources like CSS/JS/images and so forth. The advantage of that approach is that custom components can package their resources where they belong (in the JAR file, together with the component classes), and applications that use the custom components don't need to care about packaging those static resources. Weblets takes care of serving the resources to the client (taking into account that resources need to be cached/versioned/....).

Will that type of functionality also be available in JSF 2.0, or do you still see a need for a third party framework such as Weblets with JSF 2.0 ?

Posted by Davy De Waele on November 24, 2008 at 11:43 PM PST #

Why does the h:outputStylesheet tag have a target attribute if its value is ignored?

Posted by robertDobalina on May 07, 2009 at 01:12 AM PDT #

What are the possible values for the target attribute? Is it only "head" or "body" or is it any id="xx" that you can apply on a component?

Also in your code you have: <h:head id="head">; is the target="head" refering to the id="head" or the h:head ?

It's working for me for outputStyleSheet, but not for outputScript:

<h:outputScript library="js" name="jquery-1.3.2.min.js" target="head"/> is still rendered in the html body and not in the h:head.
The only difference I can see is that my page is using a ui:composition="template.xhtml" which has the <h:head> tag in it.

How would I get this working?

Thanks,
Wim

Posted by Wim Bervoets (javablog.be) on May 25, 2009 at 01:49 AM PDT #

Post a Comment:
Comments are closed for this entry.
About

user12615560

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today