Menu

Database resource bundle in a JSF application

Goal

To have a JSF application internationalized using a database resource bundle

Description

This recipe explains the basic steps that are required to create a database resource bundle solution and to use it in a JSF application

How to

The following are the steps necessary to make this recipe work:

Create the resource bundle extension that will delegate the resources lookup in a database control

Create the necessary extensions for different locales (this is the ugliest part of the solution but it is necessary; on the other hand, it is really easy to generate a couple of classes for different locales and this recipe will show you how)

Create the database tables and the corresponding JPA entities

Create the resource bundle control that will get the contents from the database

The previous class provides the implementation for the default locale. The most important part is the setParent() call inside the constructor with a locale as parameter where, basically, the resource bundle delegates the resource lookup in a parent where the implementation is delegated to an instance of the DBControl class.

Next, we will create several extensions to the previous class, one for each of the locales we want to support in our application. Each class will be similar to the following:

The database layer is implemented using JPA with the entities ResourceBundle, which constitutes the bundle name and locale, and a list of associated ResourceMessage entities, which represent the actual message key and value for each bundle.

First, the ResourceBundle implementation class (it extends from a DefaultDomainObject class which is a JPA mapped superclass that defines a primary key of type long and a version attribute of type long too – the code is not shown because it is not relevant for this recipe):

The previous implementation defines the “db” format as the only format that is supported by the implementation and the actual implementation is delegated to an extension of the class ListResourceBundle where the method getContents() is implemented in a way that the ResourceBundleService, which is the local business interface of an EJB that fetches persistent ResourceBundle instances from the database, according to the locale passed in (this implementation does not take into account the name of the resource bundle in the database) and iterates through the ResourceMessage instances associated to it.

Finally, all we have left to do is to specify the resource bundle as the one that should be used in our JSF application, through its definition in the faces-config.xml file:

Additionally, and in order to make it possible to reload the resource messages contained in the database and make the application be possible to change its messages without redeploy, an application scoped class observing a CDI event being thrown (the trigger of the event, although not shown, should be assumed to be thrown on an interface where the user is allowed to change the persistent resource messages values) was supplied that will clear the cached resources and force a re-fetch from the database the next time a resource bundle key is asked for:

Notice, in the previous implementation, the need to refresh the JSF application bundle as well, because JSF also caches the loaded resource bundles.

As an improvement, you can also add an AspectJ aspect that advises the call to java.util.ResourceBundle.getObject() and java.util.ResourceBundle.getString() so that the exception MissingResourceException is not thrown and a resource message prefixed and suffixed by the string “???” is returned instead:

Explanations

The previous recipe was implemented in a framework developed at Linkare named internally as eFragment (because it takes advantage of the Servlet 3.0 web fragments together with CDI to create a modular and extensible Web application) and is already serving different applications successfully. The implementation makes it possible to load all labels from within the database and even to change those labels dynamically without the need to restart the server through listening to CDI events that, on triggering, will clear and thus force reloading resource bundles.