Monday, February 13, 2012

Re-routing JSF resource requests with RichFaces Resource Mapping

Note: The implementation contains issue, thus the samples from this blog needs to be slightly modified, details in this comment. The fix will be available with RichFaces 4.2.1.CR1.

RichFaces resource mapping can save your life when you need to serve a different resource (JS, CSS, image) file than the one originally requested. It works in the stage of determination of the resource request path.

Specifically in all following situations, it may be really handy:
  • providing alternative versions of a resource
  • map several resources to one
  • using external resources
  • moving resources to servers with static content
Before diving deeper into the situations above, let's look at how resources typically works in JSF.

Resource loading in the picture

Component libraries bundle resource dependencies (CSS stylesheets, JavaScript sources, images) in the distribution archives (JARs) and application-specific resources are bundled in WAR - the situation is outlined on the following picture:

Component libraries (JARs) and application web archive (WAR)
and resource dependencies (green)


Let's look at all of mentioned situations one by one:

Providing alternative file

For example, your application requests jquery.js resource, but you don’t want to use default one, you want to provide alternative, maybe patched version to solve some issues. So you provide RichFaces the mapping using following configuration.

Create the file META-INF/richfaces/static-resource-mappings.properties on the classpath of your project and configure one mapping:

jquery.js=jquery-alternative-version.js


All requests for jquery.js will then be served as requests for jquery-alternative-version.js.

jquery.js mapped to jquery-alternate.js

Warning: Resource mapping requires resource servlet for its work - it is automatically registered in Servlets 3.0 environments (JBoss AS 6 and 7, Tomcat 7, GlassFish 3, etc.), but you will need to register that manually in Servlets 2.5 or lower environments - see RichFaces Developer Guide for details of how configure it.

Note: This mapping file needs to be placed in one of following locations: 

{MAVEN_WAR_PROJECT}/src/main/resources/META-INF/richfaces/

{JAR}/META-INF/richfaces/ 

{WAR}/WEB-INF/classes/META-INF/richfaces/

Note: jquery-alternative-version.js needs to be placed in your project on one of following  locations (JSF resource: 

{MAVEN_WAR_PROJECT}/src/main/webapp/resources /

{MAVEN_JAR_PROJECT}/src/main/resources/

{JAR}/META-INF/resources/

{WAR}/resources/
Another example

Or you can similarly map jsf.js resource:

javax.faces\:jsf.js=patched-jsf.js
Note: notice the backslash before the : (it is the escape sequence, required in the properties file)

Map several resources to one

Another requirement comes when you are using several component libraries in one project - oh crap, and they all are based on jQuery and each of them uses another version!

One of solutions here (except using jQuery.noConflict()) is map all requests for different jquery.js versions to one. Let’s define following mapping:

# RichFaces bundled jQuery (following line is not necessary)
jquery.js=jquery.js

# PrimeFaces bundled jQuery
primefaces\:jquery/jquery.js=jquery.js

# Another project bundled jQuery
another\:jquery.js=jquery.js


Okay, now all these libraries use only one version of jQuery - the RichFaces one. ;-)

another:jquery.js mapped to  jquery.js


Using external resources

But resource mapping isn't used only for mapping requests to serve local resources, but external HTTP resources can be served as well.

Let’s show-case this on sample of mapping requests of jQuery library to CDN [1].

jquery.js=http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js

jquery.js mapped to CDN resource


Moving resources to servers with static content

With RichFaces, you can even move all your resources to a server which serves static requests (like Apache Httpd) to lighten your application server. Just map all your resources to HTTP locations.

All application resources are mapped to static server.



That's it, RichFaces Resource Mapping really can save your life! ;-)

14 comments:

  1. Thanks for this post. I assume there is no way it could be possible to map one file to more than one location, to invoke more resources? Also, does any variant of mapping (static or custom) invoke resources regardless of what tags are in the page, or is all of this actually dynamic (resources only loaded as needed)?

    ReplyDelete
  2. Hi Josh, resources are really loaded only when necessary, regarding the component definitions in the page - each component renderer has defined list of dependencies, e.g. rich:extendedDataTable: https://github.com/richfaces/components/blob/4.2.0.20120215-Final/iteration/ui/src/main/java/org/richfaces/renderkit/ExtendedDataTableRenderer.java#L67

    Regrettably mapping can't "re-route" one resource to more than one. If you have some use case which would require it, we can discuss it.

    ReplyDelete
  3. I tried the mapping it seems not working, I put the static-resource-mappings.properties into {WAR}\WEB-INF\classes\META-INF\richfaces, and I put the js into the {WAR}\resources, and I can see the link in the page changed to point to the patched.js, but it report that patched.js is missing.
    the URL in the page is:
    "/sample/org.richfaces.resources/javax.faces.resource/packed-patched.js"

    ReplyDelete
    Replies
    1. Hi Liyu,

      Did you fix your URL problem?

      I have the same issue.

      Delete
  4. org.richfaces.webapp.ResourceServlet can serve resource only from 3 libraries:

    * StaticResourceLibrary
    * CKEditorLibrary
    * RichFacesImageLibrary

    Thats why you must put your resource in org.richfaces.staticResource folder. I think this is a misunderstanding, but to date there is no other way.

    Look at my example:

    # resource location
    META-INF/resources/org.richfaces.staticResource/jquery.js

    #mapping config
    jquery.js=org.richfaces.staticResource/jquery.js
    primefaces\:jquery/jquery.js=org.richfaces.staticResource/jquery.js

    ReplyDelete
  5. Hi all,

    I've been trying to put this working on my EAR app. I've spend on this more hours that I can with no result. The main problem that I have is on loading jquery.js resource when i put Richfaces 4.2.0.Final toghether with Primefaces 3.2.

    I'm not able to configure resource mapping for load my alternative version of richfaces jquery.js resource, the only config that (partially) works is load from http://code.jquery.com/jquery-1.7.1.js with this

    jquery.js=http://code.jquery.com/jquery-1.7.1.js

    Please, could some caritative soul give a working proof of concept for resource mapping?

    I have yet view and review the article (thanks Lukáš) but I still confused about the correct location for my alternative resource, because I tried so many locations and only get broke the resource resolution, but not get to work as I think/wish.

    Another point is that, when I try to use resource mapping file for primefaces's jquery resolution, resolution gets broken, I was trying this

    primefaces\:jquery\jquery.js=jquery-alternative.js

    Thanks in advance.

    ReplyDelete
  6. Hey guys,

    this is really the issue I have introduced with limiting resources to specific libraries, thanks for pointing to that!

    I have reported it here: https://issues.jboss.org/browse/RF-11888

    Currently, as Grzegorz said, you need to move all the resources to some of those libraries, it means e.g.:

    # the configuration for mapping:
    jquery.js=org.richfaces.staticResource/jquery-alternative-version.js

    # where should the resource be located:
    {MAVEN_WAR_PROJECT}/src/main/webapp/resources/org.richfaces.staticResource
    {MAVEN_JAR_PROJECT}/src/main/resources/org.richfaces.staticResource/
    {JAR}/META-INF/resources/org.richfaces.staticResource/
    {WAR}/resources/org.richfaces.staticResource/

    ReplyDelete
  7. Thanks a lot guys!!

    My proof of concept is now working, and I understand the actual resource loading process, well, almost a part of the process ;)

    Lukáš, would you mind note this at the community richfaces doc?

    I have created a github repository with my proof of concept, available at https://github.com/jotraverso/Richfaces-Primefaces

    ReplyDelete
  8. Keeping on the track...

    After the successfully pof I tried to put this on the real EAR application and I see another issue within JBoss AS7 7.1.0.Final.

    After a few tries the EAR is running, but have to put all richfaces, primefaces a my fixing jar on WAR's lib directory (using maven of course).

    It seems that the fix must be visible at same classloading level that richfaces's jars.

    ReplyDelete
  9. Hey Jorge, the issue [1] has been fixed in 4.2.1-SNAPSHOT and will be released in 4.2.1.CR1 soon, therefore I decided to do not mention that in the docs.

    [1] https://issues.jboss.org/browse/RF-12093

    ReplyDelete
  10. Jorge, regarding the EAR issue, you can give a try to read Class Loading in AS7.

    I'm just guessing, but the configuration file would probably need to be exported.

    ReplyDelete
  11. Hi Lukáš,

    My NON working EAR structure was like that:

    myapp.ear
    ---lib
    ---- richfaces's jars
    ---- more shared jars
    --- one-ejb.jar
    --- another-ejb.jar
    --- myapp.war
    ---- WEB-INF/classes/META-INF/resources
    ---- /richfaces/static-resource-mappings.properties
    ---- /org.richfaces.staticResource/jquery-alternative-version.js
    ---- WEB-INF/lib/primefaces-3.2.jar


    My working ear structure is like that:
    myapp.ear
    ---lib
    ---- more shared jars
    --- one-ejb.jar
    --- another-ejb.jar
    --- myapp.war
    ---- WEB-INF/classes/META-INF/resources
    ---- /richfaces/static-resource-mappings.properties
    ---- /org.richfaces.staticResource/jquery-alternative-version.js
    ---- WEB-INF/lib/primefaces-3.2.jar
    ---- WEB-INF/lib/richfaces's jars

    And now the mapping files and my custom resource could be stored at WEB-INF/classes, directly at webapp /resources folder or at /META-INF/resources insde a JAR at WEB-INF/lib (like my github example)

    Further I'll keep Class Loading in AS7 doc on my favorites.

    Thanks again.

    ReplyDelete
  12. Hi Lukáš,

    I am facing one issue regarding to resource loading.
    we are using richfaces 4.5.2.final , jboss seam, JSF 2 frameworks.
    I placed all richfaces jars in ear lib.
    My application is installed successfully and I am able to access my application using browser.
    But i am not able to access richfaces components.
    When i looked into firefox console , i am seeing errors which says jquey.js ,richfaces.js files are not accessible/available under war context.

    How to make it available for war context.

    Thanks in advance .

    ReplyDelete