My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

duminică, 18 ianuarie 2015

JSF 2.2 Resources and ResourceHandlers

Starting with JSF 2.0, the web resources, such as CSS, JavaScript, and images are loaded from a folder named /resources, present under the root of your web application or from /META-INF/resources in JAR files. Under the /resources folder, we can optionally add a library (or theme), which is like a collection of client resources artifacts - here we group all the resources. We can also create a special folder matching the regex \d+(_\d+)* under the library folder for providing versioning. In this case, the default JSF resource handler will always retrieve the newest version to display. We can take advantage of library automatic version management and  resource version management. Moreover we can use locale prefix for locale management.

Note If your application doesn't need a library then just omit it (e.g. some personal applications). Nevertheless, if you decide to use a library name, but you don't have a clear purpose, then a name as default or common, or your company name may be a good choice. Don't use the value of the library attribute to simply repeat what the tag name already indicates (e.g. css, js, images).

In the most "decent" common scenario, we will have a library (let's name it, default) and the resources will be grouped in this library in different sub-folders that reflect the resources type, such, css files, images files, JavaScript files, etc. In the figure below, you can see this scenario and some examples of loading the sample.css, sample.js and sample.png by a JSF page author. Notice that this simple scenario covers only a crumb of resourceIdentifier - under this notion, we have a path that it is recognized by the default ResourceHandler and that is composed of:

[localePrefix/][libraryName/][libraryVersion/]resourceName[/resourceVersion]

Let's name this scenario A:

So, let's have some conclusions:
·          JSF identifies a resource request through the string, javax.faces.resource
·         When we specify the library name, it will be reflected in a GET URL as a request parameters named, ln. The value of this request parameter is the value of the library attribute.
·         When we omit the library attribute, and use the name attribute for indicate the entire resource path, JSF will not recognize the library (the ln request parameter is not added), which allows us to break down the automatic versioning management and load a specific resource.

Further, let's have a scenario that involves library automatic version management. For this, let's add under the library folder (default) two folders matching the regex \d+(_\d+)*. One will be 1_0, and the other one will be 2_0 - of course, you can have more (e.g. 1_2_2, 2_0_1_1, etc). The idea is to notice that JSF will automatically detect the most recent version and will load the resources from version 2_0.

Let's name this scenario B:

Beside the conclusions from scenario A, let's have a few more:
·         When the library is explicitly set, JSF uses the library automatic version management and detects the most recent version. This is reflected in the GET URL via the v request parameter. This is added next to the ln request parameter.
·         When we omit the library attribute, and use the name attribute for indicate the entire resource path, JSF will not recognize the library version (the v request parameter is not added), which allows us to break down the automatic versioning management and load a specific resource.
·         When we break down the automatic versioning management we can load resources from older versions.

As we said before, JSF supports library versioning and resources versioning. In the next scenario, we focus on an example that uses both.

Let's name this scenario C:

Now, next to conclusions from scenario A and B, we have:
·         First is important to notice that the /css, /js and /images folders from scenarios A and B has been renamed as sample.css, sample.js and sample.png. The new names are used as the values for the name attributes of the <h:outputStylesheet>, <h:outputScript> and <h:graphicImage> tags. You can name these folders as you like, but is a good practice to reflect the resource name.
·         Next, notice that under these folders we placed multiple resources files (e.g. under sample.css folder, we have 2_0.css and 2_2.css). So, each resource is renamed to reflect its version. This will help JSF ResourceHandler to determine the right resource version.
·         The resource version (e.g. 2_0) is added in the GET URL under the same v request parameter. The library version and the resource version are concatenated in a single string (e.g. 2_02_2). Of course, if the library version is missing, then only the resource version appear as the value of the v request parameter. And, if both are missing, the v is not added at all.
·         When we omit the library attribute, and use the name attribute for indicate the entire resource path, JSF will not recognize the library/resource version (the v request parameter is not added), which allows us to break down the automatic versioning management and load a specific resource.
·         When we break down the automatic versioning management we can load resources from older versions.

As you can see in scenario C, the resourceIdentifier is almost completely exploited. Finally, we have an example that take advantage of JSF locale management.

Let's name this scenario D:

Now, next to conclusions from scenario A, B and C, we have:
·         Even if they are "related" or maybe is better say that they "work together", do not confuse JSF locale with JSF locale prefix
·         Notice that locale prefix can be any text, not necessary to the supported codes for countries
·         The locale prefix is reflected in the GET URL as the loc request parameter.
·         When we omit the library attribute, and use the name attribute for indicate the entire resource path, JSF will not recognize the locale prefix (the loc request parameter is not added), which allows us to break down the locale prefix management and load a specific resource.
·         When we break down the locale prefix management we can load resources from any locale prefix.

Sometimes you may need to "force" JSF to use a locale prefix (e.g. when the user make a selection in page). Basically, for this you have to accomplish several steps as follows:

1.       For each supported locale provide a properties file (e.g. MyLocales_en.properties, My_Locales_fr.properties, MyLocales_ro.properties).
2.       In each of these files add a name=value pair, where the name is always javax.faces.resource.localePrefix and the value is the name of the folder under the /resources (e.g. javax.faces.resource.localePrefix=english).
3.       In faces-config.xml, under <application><locale-config> tag, add the supported locales via <supported-locale> tag (e.g. <supported-locale>en </supported-locale>) and the default locale via <default-locale> tag.
4.       Still in faces-config.xml, under <application> <message-bundle> add the properties file base name via <base-name> tag (e.g. <base-name>my.samples.MyLocales</base-name>). Do not add the <var>.
5.       At test time, switch the locale using the declarative (e.g. <f:view locale="en">) or programmatically (e.g. FacesContext.getCurrentInstance().getViewRoot().setLocale(Locale.ENGLISH);) approach.
6.       Well, finally, take care that the Accept-Language request header support your locales and the resources caching will not cause issues like returning the same image at two different requests (two different loc values). In Mozilla Firefox ,you can "play" with the Accept-Language request header using the Quick Accept-Language Switcher add-on.

Personally, I don't like this approach. Per example, let's suppose that we have a website of a travel agency and the user may select to see pictures from different countries. For each country we have the locale, the defined prefix locale and the corresponding folder, but this doesn't mean that if the locale is changed (declaratively or programmatically) everything will work smoothly and the images will be picked up from the correct folder. Per example, if the Accept-Language request header will not support a particular language, then images will come from another folder. So, in such cases I prefer a more simplest approach, which consist in:
1.       Name the folders exactly as locales (e.g. en instead of english, fr instead of french)
2.       Indicate the locale name in the library path, as below:

<h:outputStylesheet library="#{facesContext.viewRoot.locale}/library" name="resource"/>
<h:outputScript library="#{facesContext.viewRoot.locale}/library" name="resource"/>     
<h:graphicImage value="#{resource[facesContext.viewRoot.locale+='/library:resource']}"/>

This will lead to GET URLs without the loc:
.../faces/javax.faces.resource/resource?ln=locale/library

So, this is how the JSF default resource handler deals with resources. But what can we do if we don't respect this inflexible structure of folders? For example, if we have the CSS files under the application web root in /samples/css/, or we want to place resources in a protected folder, such as WEB-INF. In this case, there is no directly accessible resources folder and we have no idea what a library is. If we write something like the following code, it will not work:

<h:outputStylesheet name="resource" />

Among the possible solutions, we have the facility to write a custom resource handler. In order to write a custom resource handler, we have to respect several rules, as follows:

1.       Extend the ResourceHandlerWrapper class.
2.       Write a delegating constructor. JSF will call this constructor for passing the standard resource handler, which we will wrap in a ResourceHandler instance.
3.       Override the createResource method. Here, we can sort the resources and decide which of them go to the default resource handler and which of them go to our custom resource handler.

So, our resource is named sample.css and it lies in samples/css folder under application web root. In order to instruct JSF how to find it, we can follow the above steps and write a custom resource handler, as below - notice the JSF 2.2 createResourceFromId() method:

public class CustomResourceHandler extends ResourceHandlerWrapper {

 private final ResourceHandler wrapped;

 public CustomResourceHandler(ResourceHandler wrapped) {
  this.wrapped = wrapped;
 }

 @Override
 public Resource createResource(String resourceName, String libraryName) {

  if (!resourceName.equals("sample.css")){
      //return super.createResource(resourceName, libraryName);  //in JSF 2.0 and JSF 2.2
      return super.createResourceFromId(libraryName + "/" + resourceName); //only in JSF 2.2
  } else {
    return new SampleResource(resourceName);
  }
}

 @Override
 public ResourceHandler getWrapped() {
  return this.wrapped;
 }
}

So, when the resource name is sample.css we delegate the control to our custom resource, named SampleResource. We need a custom resource because we need to override the getRequestPath() method and return the "correct" path. A custom resource can be written by extending the ResourceWrapper class and override the needed methods. Per example, the SampleResource is listed next:

public class SampleResource extends ResourceWrapper {

 private final String resourceName;

 public SampleResource(String resourceName) {
  this.resourceName = resourceName;
 }

 @Override
 public String getRequestPath() {
  return "samples/css/" + this.resourceName;
 }

 @Override
 public Resource getWrapped() {
  return this;
 }
}

Finally, we need to instruct JSF to use our custom resource handler instead of the default one, and for this we need to add in faces-config.xml the next snippet of code:
...
<application>
 <resource-handler>my.custom.resource.handler.CustomResourceHandler</resource-handler>
</application>
...

Now, we can use the resource in a JSF page as:
<h:outputStylesheet name="sample.css"/>

However, remember that I said "Among the possible solutions ..."? Well, starting with JSF 2.2, we can indicate the folder of resources through a context parameter in the web.xml descriptor, as follows (mapped by the ResourceHandler.WEBAPP_RESOURCES_DIRECTORY_PARAM_NAME field):

<context-param>
 <param-name>javax.faces.WEBAPP_RESOURCES_DIRECTORY</param-name>
 <param-value>/samples/css</param-value>
</context-param>

Starting with JSF 2.2 we can use dependency injection in resource handlers. Per example, you can randomly load between sample_1.css, sample_2.css, ... sample_n.css by generating the resource name in a simple bean as below:

@Named
@RequestScoped
public class ResourceNameBean {

 private final String resourceName;

 public ResourceNameBean() {
  this.resourceName = "sample_" + (new Random().nextInt(n) + 1) + ".css";
 }

 public String getResourceName() {
  return resourceName;
 }
}

Afterwards, declare a dummy CSS in your JSF - the dummy CSS doesn't need to exist:

<h:outputStylesheet library="default" name="dummy"/>

Now, write a custom resource handler as below:

public class CustomResourceHandler extends ResourceHandlerWrapper {

 @Inject
 private ResourceNameBean resourceNameBean;
 private ResourceHandler wrapped;

 public CustomResourceHandler(){       
 }
   
 public CustomResourceHandler(ResourceHandler wrapped) {
  this.wrapped = wrapped;
 }

 @Override
 public ResourceHandler getWrapped() {
  return this.wrapped;
 }

 @Override
 public Resource createResource(String resourceName, String libraryName) {
  return super.createResource(resourceNameBean.getResourceName(), libraryName);
 }
}

You can check more complex custom resource handlers in OmniFaces source code.

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors