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