JSF Techniques – EL Resolvers

- Damodar Chetty

- Feb 03, 2009

 

In this episode, I’m going to discuss a rather esoteric topic – how JavaServer Faces resolves the bindings that you specify in your templates, you know the ones that look like “#{variableName.propertyName}”.

We’ll also look at how to write our own custom resolvers.

1.1. EL Expressions

Note: Though I focus primarily on value expressions, the concepts are almost identical for method expressions as well.

The prototypical expression above can actually take two forms:

#{variableName.propertyName}” or

#{keyword.variableName.propertyName}

The first example indicates to JSF that you are looking for an object stored with the logical name “variableName” in one of the servlet scopes (request, session, or application); and that you then need to invoke getPropertyName() on that returned object. The value returned by this getter is what this expression is “resolved” to.

The second case is a more explicit formulation of the expression, and indicates to the resolver where it should begin looking for the desired variable.

Keywords are reserved words that represent the various application scopes (such as applicationScope, sessionScope or requestScope) or other special objects that are present in the JSF architecture (such as facesContext, view, etc.)

The power of EL expressions comes from the fact that properties can be nested as deep as you would like, for e.g., “#{keyword.variableName.propertyName.anotherPropName}”.

Therefore, the general form for an EL expression can be written as: #{[keyword.]?variableName[.propertyName]*},

i.e. an optional keyword followed by a object’s logical name, followed by zero or more optional property names. So, the following are all valid expressions:

keyword.variableName.propertyName.anotherPropertyName.yetAnotherPropertyName

keyword.variableName.propertyName

keyword.variableName

variableName.propertyName.anotherPropertyName

variableName.propertyName

variableName

 

Note: EL can support logical and arithmetical components in its expressions. However, this article focuses on the basic mechanism used to resolve variable and property references.

1.2. Resolving Expressions

Resolution, in this context, is the process by which JavaServer Faces replaces one of the above expressions, starting with # and ending with the closing brace, by an appropriate value.

Let’s consider the general expression:

#{variableName.propertyName.anotherPropertyName}

As we saw earlier, this is equivalent to locating the bean with the logical name “variableName” in one of the scopes and then invoking variableName.getPropertyName().getAnotherPropertyName().

The logical variableName that we refer to here, is the logical name associated with the actual object using either JSF’s native Managed Bean Creation Facility, or Spring.

<bean id="loginBean" class="com.swengsol.LoginBean" scope="request" />

In this case, the logical name “loginBean” is used to represent an instance of the com.swengsol.LoginBean class, that is found in the Request scope.

1.3. The Resolver

The object that assists JSF in resolving an expression is known as a Resolver. And, since this is an Expression Language (EL) expression, this resolver is termed an EL Resolver.

In JSF 1.1, this resolution mechanism was handled using a combination of a Variable Resolver and one or more Property Resolvers. The Variable Resolver was tasked with finding the appropriate object that matched the left most portion of the expression, and the Property Resolvers handled each additional portion of the expression.

This mechanism underwent significant changes between JSF 1.1 and 1.2, primarily because the javax.faces.el package was deprecated in favor of javax.el, due to EL becoming a top level API in its own right.

In JSF 1.2, the Variable Resolver and Property Resolver classes are replaced by a single javax.el.ELResolver that parses the binding expression and resolves it to a final value.

Normally, you will be quite happy with the standard resolution mechanisms provided by JavaServer Faces, and with the extended mechanism provided by Spring. However, there are often cases when even happiness is not enough.

The joy of using JavaServer Faces is best felt at these moments. It is infinitely expandable in every direction imaginable. Of course, flexibility and pluggability are never free, and this characteristic of JSF drives many bloggers to rail against its complexity.

In this case, we’ll use this “complexity” to our advantage by showing how you might extend the EL implementation.

1.4. The Problem – Delegated Localization

The traditional approach to internationalization is to load a resource bundle and to map it to a variable name, which could then be used to look up resources from that bundle.

E.g.,
<f:loadBundle baseName=”com.swengsol.HomePageBean” var=”msgs” />

<h:outputText value=“#{msgs.loginName}”

This causes the key “loginName” to be looked up in the appropriate resource file, say, either HomePageBean_es.properties or HomePageBean.properties in the com.swengsol package.

Consider the case where you need localization support with a twist.

Let’s say you need to define message strings at the application level using the application’s message bundle. However, you’d like to allow developers to supply overrides for these messages in resource bundles that accompany individual managed beans.

I.e., when a template, say HomePage.xhtml wants to refer to a message key, we want to first look in its associated backing bean’s resource bundle, and if it is not found in there, we want to proceed up and check the application’s message bundle instead.

To top it all off, we’d like to retain the translation support that comes with language specific resource bundles.

Finally, we’d like to retain the elegance of the above expressions to access the resource bundles.

Let’s demonstrate this with an example:

com.messages.properties:

genericMessage=Generic HELLO from messages.properties

overriddenMessage=Base HELLO from messages.properties

 

 

com.swengsol.HomePageBean.properties:

overriddenMessage=Overridden HELLO from homePageBean.properties

 

Assume the template HomePage.xhtml is backed by an instance of HomePageBean.java; and that the template LoginPage.xhtml is backed by an instance of LoginPageBean.java.

Use case #1:

If either of these templates refers to genericMessage we want it to be resolved to the value from the application message bundle – i.e., to “Generic HELLO from messages.properties”.

Use case #2:

If the HomePage.xhtml template refers to the key overriddenMessage we want it to be resolved to the value from the message bundle associated with its managed bean – i.e., to “Overridden HELLO from homePageBean.properties”.

However, if the LoginPage.xhtml template refers to the same key, we want it to resolve to the string “Base HELLO from messages.properties”.

Use case #3:

Finally, if LoginPageBean.java only needs to use generic application level messages, it should function just fine without a resource bundle.

Use case #4:

Lets add com.messages_es.properties and com.swengsol.HomePageBean_es.properties to the mix. Now, for a browser locale of “es”, we want to see the same behavior of Use Cases #1 and #2, just internationalized appropriately.

For expediency, I strongly suggest that you overlook my ghastly attempt at translation.

com.messages_es.properties:

genericMessage=Generic HOLA from messages.properties

overriddenMessage=Base HOLA from messages.properties

 

 

com.swengsol.HomePageBean_es.properties:

overriddenMessage=Overridden HOLA from homePageBean.properties

 

1.5. The Solution

This seems like a daunting problem indeed. Now how do you think we might solve this sticky issue? A custom EL Resolver, you say? That was quick!

1.5.1. Defining a custom EL expression

We begin by defining the custom syntax for how a template might refer to these delegating messages.

We’re talking about resource bundles now, so the standard syntax of having the expression language map to JavaBean accessor methods does not quite fit right. So let’s begin by reframing our generic expression: #{[keyword.]?variableName[.propertyName]*}

instead as

#{[keyword.]?logicalName[.logicalName]*}.

While this may seem a minor difference, it is actually not. It indicates that it is the resolver that imparts meaning to how a logical name is interpreted, and that there is nothing sacred about variable names and property names. As a result, this lets us start down the path of defining our own custom syntax.

Next, we’ll nail this down further to:

#{delegatedLocalizer.beanName.propertyKey}


delegatedLocalizer is a unique keyword that will help our delegated localization resolver to identify those expressions that it should handle. When our resolver sees this it stakes its claim to the expression, and JSF will then keep calling it with parts of the expression until it is completely resolved.

beanName will be interpreted by our resolver as the logical name of the bean that will help us determine which resource bundle we should start from; and

propertyKey is the key that will be used by our resolver to look up the appropriate resource, either within the resource bundle associated with the specified bean, or within the application’s resource bundle.

We picked delegatedLocalizer as our special keyword because it is unique enough not to clash with any of the other special keywords (such as sessionScope and facesContext) that are recognized by the other JSF resolvers.

Note that our resolver’s expression pattern does not use wildcards. I.e., all parts of the expression are required, and only one of each must be specified.

1.5.2. Configuration Changes

Note: I have discussed setting up projects numerous times before, and I’ll defer to those articles, pointing out only relevant changes here.

 

Our Spring application context defines two managed beans, and their logical names. Note these logical names – they will be used later in the templates and by our delegatedLocalizer.

WEB-INF/applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <bean id="homePageBean"  class="com.swengsol.HomePageBean"  scope="request" />

  <bean id="loginPageBean" class="com.swengsol.LoginPageBean" scope="request" />

</beans>

 

Our faces configuration file defines our variable resolvers. In particular, note the presence of our custom delegated localizer implementation – within the <el-resolver> element, which is new for JSF 1.2. The Spring resolver uses the deprecated mechanism of variable resolvers.

Also note the localization information – we indicate the locales that we intend to support; as well as preload the application’s message bundle via the <message-bundle> element, and the individual resource bundles via the <resource-bundle> elements. In particular, note the logical name to which each resource bundle is bound.

WEB-INF/faces-config.xml:

<?xml version='1.0' encoding='UTF-8'?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee       http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"

  version="1.2">

  <application>

  <variable-resolver>

     org.springframework.web.jsf.DelegatingVariableResolver

   </variable-resolver>

   <el-resolver>

     com.swengsol.resolver.DelegatedLocalizationResolver

   </el-resolver>

   <locale-config>

     <default-locale>en</default-locale>

     <supported-locale>es</supported-locale>

   </locale-config>

   <message-bundle>com.messages</message-bundle>

   <resource-bundle>

     <base-name>com.swengsol.HomePageBean</base-name>

     <var>homePageMessages</var>

   </resource-bundle>

  </application>

</faces-config>

1.5.3. Managed Beans

All our managed beans extend the PageBean class. This class ensures that each managed bean supports the getResourceBundleName() operation, which will be required by our custom resolver. The default implementation does nothing – and is a convenience that ensures that beans that don’t need localization don’t have to provide an implementation.

com.swengsol.PageBean.java:

package com.swengsol;

import java.io.Serializable;

public class PageBean implements Serializable {

  private static final long serialVersionUID = -1L;

  public String getResourceBundleName() {

   return null;

  }

}

 

Our templates are backed by managed beans that would hold actions, listeners, etc. In this simple case, they only implement our getResourceBundleName() method.

com.swengsol.HomePageBean.java:

…lines omitted…

public class HomePageBean extends PageBean {

  private static final long serialVersionUID = -1L;

  @Override

  public String getResourceBundleName() {

   return "homePageMessages";

  }

}

com.swengsol.LoginPageBean.java … uses only the application level bundle:

public class LoginPageBean extends PageBean { }

1.5.4. Templates

Our templates are very basic – and simply showcase our new resolver’s usage. As you can see, the same syntax is used irrespective of where the message ends up being retrieved from.

HomePage.xhtml:

<!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:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">

  <head>

  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

  <title>Custom Resolver</title>

  </head>

  <body>

    <h1>Home Page</h1>

    <strong>delegatedLocalizer.homePageBean.genericMessage ... </strong>

    <br />

   <h:outputText value="#{delegatedLocalizer.homePageBean.genericMessage}" />

    <p />

    <strong>delegatedLocalizer.homePageBean.overriddenMessage ...</strong>

   <br />

   <h:outputText value="#{delegatedLocalizer.homePageBean.overriddenMessage}" /> 

  </body>

</html>

 

 

 

LoginPage.xhtml … identical to LoginPage.xhtml except for:

<h1>Login Page</h1>

<h:outputText value="#{delegatedLocalizer.loginPageBean.genericMessage}" />

<h:outputText value="#{delegatedLocalizer.loginPageBean.overriddenMessage}" />

<strong>delegatedLocalizer.loginPageBean.nonexistentMessage ... </strong>

<br />

<h:outputText value="#{delegatedLocalizer.loginPageBean.nonexistentMessage}" />

1.5.5. DelegatedLocalizerResolver

Now to the main event. The star of the show. Our implementation of the ELResolver abstract class – heeeere’s com.swengsol.resolver.DelegatedLocalizationResolver.java.

Note: This logic is fairly complex, so feel free to shoot me an email if you think my description is lacking, and I’ll update this appropriately.

 

package com.swengsol.resolver;

 

import org.apache.commons.lang.StringUtils;

import javax.el.ELResolver;

import javax.el.ELContext;

import javax.el.PropertyNotFoundException;

import javax.faces.context.FacesContext;

import javax.faces.application.Application;

import java.beans.FeatureDescriptor;

import java.util.Iterator;

import java.util.ResourceBundle;

import java.util.MissingResourceException;

import com.swengsol.PageBean;

 

public class DelegatedLocalizationResolver extends ELResolver {

  @Override

  public void setValue(ELContext context, Object base, Object property, Object value) {

   if (base instanceof TokenManager) {

     throw new PropertyNotFoundException("Unable to set a value on a readonly variable");

   }

  }

 

  @Override

  public boolean isReadOnly(ELContext context, Object base, Object property) {

   if (base instanceof TokenManager) {

     context.setPropertyResolved(true);

     return true;

   }

   return false;

  }

 

  @Override

  public Object getValue(ELContext context, Object base, Object property) {

   if (base == null && "delegatedLocalizer".equals((String)property)) {

     context.setPropertyResolved(true);

     return new TokenManager();

   }

   if (base instanceof TokenManager && property instanceof String) {

     context.setPropertyResolved(true);

     final TokenManager tokenManager = (TokenManager)base;

     final int currentTokens = tokenManager.getCurrentTokens();

     switch (currentTokens) {

       case 0:

         tokenManager.setBeanName((String)property);

         break;

       case 1:

         tokenManager.setPropertyName((String)property);

         break;

       default:

         throw new PropertyNotFoundException("Invalid number of parameters.");

     }

     tokenManager.setCurrentTokens(currentTokens + 1);

     if (tokenManager.getCurrentTokens() < 2) {

       return base;

     } else {

       return tokenManager.resolveMessage();

     }

   }

   return null;

  }

 

  @Override

  public Class<?> getType(ELContext context, Object base, Object property) {

   if (base instanceof TokenManager && property instanceof String) {

     context.setPropertyResolved(true);

     return Object.class;    //only common class for TokenManager and String.

   }

   return null;

  }

 

  @Override

  public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,Object base){

   return null;

  }

  @Override

  public Class<?> getCommonPropertyType(ELContext context, Object base) {

   return null;

  }

 

  public static class TokenManager {

   private int currentTokens;

   private String beanName;

   private String propertyName;

   public TokenManager() { currentTokens = 0; }

   public int getCurrentTokens() { return currentTokens; }

   public void setCurrentTokens(int currentTokens) { this.currentTokens = currentTokens;}

   public String beanName() {return beanName; }

   public String propertyName() {return propertyName; }

  public void setBeanName(String beanName) { this.beanName = beanName; }

   public void setPropertyName(String propertyName) { this.propertyName = propertyName; }

 

   public String resolveMessage() {

     if (currentTokens < 2 || StringUtils.isBlank(beanName) ||

                           StringUtils.isBlank(propertyName)) {

       throw new PropertyNotFoundException("Unable to resolve:" + propertyName);

     }

     final FacesContext facesContext = FacesContext.getCurrentInstance();

     final Application app = facesContext.getApplication();

     final PageBean bean = (PageBean)facesContext.getApplication().evaluateExpressionGet(
     facesContext, "#{" + beanName + "}", PageBean.class);

    ResourceBundle rb1 = null;

     if (!StringUtils.isBlank(bean.getResourceBundleName())) {

       rb1 = app.getResourceBundle(facesContext, bean.getResourceBundleName());

     }

     String message = null;

     boolean found = true;

     try {

       if (null != rb1)

         message = rb1.getString(propertyName);

       else

         found = false;

     } catch (MissingResourceException mre) {

       found = false;

     }

     if (!found) {

       found = true;

       final String bundleName = app.getMessageBundle();

       final ResourceBundle rb2 = ResourceBundle.getBundle(bundleName,
                                  facesContext.getViewRoot().getLocale());

       try {

         message = rb2.getString(propertyName);

       } catch (MissingResourceException mre) {

         found = false;

       }

     }

     if (!found) {

       message = "{" + propertyName + "}";

     }

     return message;

   }

  }

}

 

This is quite a mouthful so I’m going to start slow. We’ll explore how resolution works in general; then look at the methods in the ELResolver abstract class that we are required to implement; and then look at our mechanism of maintaining state while the resolution process in underway.

1.5.5.1. Resolution Overview

When JSF needs to resolve a particular EL expression, it will start by invoking the resolvers registered for a particular application as well as its built-in resolvers.

The first resolver that recognizes the expression as something that it can handle, lays claim to the resolution task, and tells the rest to back off. JSF then uses that resolver until that expression has been completely resolved.

This is how the Spring resolver is able to step in before the built in resolver to resolve references to instances of managed beans and other scoped variables. In faces-config.xml, you will see that our own resolver is next in line after the Spring resolver, and will take over if the Spring resolver is unable to understand the keyword portion of the expression.

1.      JSF passes in the keyword “delegatedLocalizer” first to the Spring variable resolver. This resolver does not recognize this as a special keyword, and so will treat it as a logical name for a variable in one of its scopes. After failing to locate the variable with this name in any of its scopes, the Spring resolver will give up and return.

2.      JSF then passes the keyword to our DelegatedLocalizationResolver, which immediately recognizes the keyword as belonging to it. It tells JSF to use it as the resolver for this expression.

It returns an object instance that will retain the ongoing state for the current resolution process.

3.      JSF calls our resolver again, passing it the next part of the expression – the logical bean name, along with the retained-state object instance it received in step 2 from our resolver for this expression.

Our resolver stores the logical bean name in the retained-state object, and returns that object back to JSF.

4.      JSF calls our resolver yet again, passing it the next part of the expression – the property key, along with the retained-state object instance it received in step 3 from our resolver for this expression.

a.      Our resolver stores the property key in the retained-state object, and realizes that it has enough to resolve the expression.

b.      It asks JSF to resolve the logical bean name to a bean present in one of the servlet scopes. This resolution kicks off a separate resolution process, which is handled this time by Spring, which returns the appropriate bean.

c.      Our resolver requests the looked up bean for the name of the resource bundle.

d.      Our resolver then uses the resource bundle name and property key to look up the message. If the key is not found in the resource bundle, it then delegates upwards to the application’s message bundle.

e.      Once the key is found, it returns the value to JSF.

5.      JSF replaces the expression with the resolved value returned by our resolver, and continues with rendering the rest of the template.

1.5.5.2. Methods overview

The methods in ELResolver can be divided into resolution methods, and meta data methods.

The resolution methods include get/setValue() which are used to get/set a value after resolution completes; isReadOnly() which indicates whether an expression is readonly; and getType() which returns the most general type that can be a superclass of all types that can be returned by getValue().

As can be seen these methods deal with resolving the current expression, and are actively called by JSF during the resolution process.

This class of methods all take a ELContext, an Object base, and an Object property. The base is null when it’s the left most portion of the expression being evaluated.

The resolution methods have two main functions. First, they should stake their claim to the expression, by invoking setPropertyResolved(true) on the ELContext argument  and returning either a state-retention object, the result of the partial resolution, or the final result of the resolution. Else, it should return null, to let JSF continue on its quest to find the appropriate resolver to handle this expression.

In our case, setValue() throws an exception, since we’re implementing a read only variable; as demonstrated by the isReadOnly() method that returns true.

getType() method returns the most general object that can be returned by getValue(). In our case, getValue() returns a TokenManager in the first 2 invocations; and a String in the final invocation. The most general object that is common to both is Object, which is what this method will return.

The metadata methods include getCommonPropertyType() and getFeatureDescriptors() both of which are implemented to return null. I don’t have a use case that requires these to be implemented.

1.5.5.3. Implementing getValue()

As we saw earlier, when JSF encounters an expression that starts with “delegatedLocalizer” it delegates it first to the Spring resolver, which is unable to handle it. JSF then hands over this expression to the next resolver in line, the DelegatedLocalizationResolver, by invoking its getValue() method.

JSF does not simply hand over the entire expression to the resolver (which would have been my preferred design choice – but I’m sure smarter people than I have thought this through.)

Instead our resolver’s getValue() method is called once per token in the expression. In our case, the expression takes the form delegatedLocalizer.beanName.propertyKey; so DelegatedLocalizationResolver.getValue() is called three times.

The first time, it is called with the base parameter set to null; and the property parameter is set to the leftmost token in the expression - “delegatedLocalizer”.

The second time it is called with the base parameter set to the variable returned by the previous (first) call to getValue(); and the property parameter set to “beanName”.

The third and final time, it is called with the base parameter set to the variable returned by the previous (the second) call to getValue(); and the property parameter set to “propertyKey”.

What this means to us is that our getValue() must handle two generic cases – the first case when the keyword is sent to us, allowing us to take control of this expression resolution; and the other case is when subsequent portions of the expression are sent in.

We handle the first case as follows:

   if (base == null && "delegatedLocalizer".equals((String)property)) {

     context.setPropertyResolved(true);

     return new TokenManager();

   }

Our resolver stakes its claim to resolving this expression by detecting that the keyword is “delegatedLocalizer” and calling setPropertyResolved(true) on the ELContext passed in. This has the effect of telling JSF that all future calls for this expression have to be channeled through our resolver. Note that we instantiate a new TokenManager() that we return from this method.

1.5.5.4. TokenManager

A few words on the TokenManager are now in order. This is a public static class that is contained within our DelegatedLocalizationResolver.

One of the side effects of calling the resolver multiple times, rather than simply handing it over the entire expression, is that the resolver must maintain the state of the resolution as it progresses. Since the resolver is designed to be stateless, it must encapsulate the current invocation’s state of the resolution into a memento that it returns to the JSF implementation. JSF then returns this memento in future calls to the resolver for the same expression.

This is the primary purpose that our TokenManager serves. In addition, we force our memento to also perform the actual evaluation of the expression once it is good and ready.

Our TokenManager records additional pieces of information each time getValue() is invoked.

   if (base == null && "delegatedLocalizer".equals((String)property)) {

     context.setPropertyResolved(true);

     return new TokenManager();

   }

As shown in the snippet above, the first time round, a new TokenManager instance is created and returned.

 

if (base instanceof TokenManager && property instanceof String) {

  context.setPropertyResolved(true);

  final TokenManager tokenManager = (TokenManager)base;

  …

  switch (currentTokens) {

  case 0:

  tokenManager.setBeanName((String)property);

   …

  }

  tokenManager.setCurrentTokens(currentTokens + 1);

  if (tokenManager.getCurrentTokens() < 2) {

  return base;

  …

 

The second time round, we set the bean name on it and since we don’t have all the pieces needed to resolve the expression, simply return the TokenManager. This tells JSF that we expect the TokenManager to be returned to us during the next invocation of getValue(). Note that there is no reason we could not return a different object – since this is typed as Object. However, our TokenManager is versatile enough for our needs.

 

if (base instanceof TokenManager && property instanceof String) {

  context.setPropertyResolved(true);

  final TokenManager tokenManager = (TokenManager)base;

  switch (currentTokens) {

  …

  case 1:

  tokenManager.setPropertyName((String)property);

   …

  }

  tokenManager.setCurrentTokens(currentTokens + 1);

  …

  return tokenManager.resolveMessage();

}

 

The third time round, we extract and record the property key. At this point, we have enough information accumulated for our resolution to succeed, so we invoke tokenManager.resolveMessage() to complete our work for us.

   public String resolveMessage() {

     if (currentTokens < 2 || StringUtils.isBlank(beanName) ||

                           StringUtils.isBlank(propertyName)) {

       throw new PropertyNotFoundException("Unable to resolve:" + propertyName);

     }

     final FacesContext facesContext = FacesContext.getCurrentInstance();

     final Application app = facesContext.getApplication();

     final PageBean bean = (PageBean)facesContext.getApplication().evaluateExpressionGet(
     facesContext, "#{" + beanName + "}", PageBean.class);

    ResourceBundle rb1 = null;

     if (!StringUtils.isBlank(bean.getResourceBundleName())) {

       rb1 = app.getResourceBundle(facesContext, bean.getResourceBundleName());

     }

     String message = null;

     boolean found = true;

     try {

       if (null != rb1)

         message = rb1.getString(propertyName);

       else

         found = false;

     } catch (MissingResourceException mre) {

       found = false;

     }

     if (!found) {

       found = true;

       final String bundleName = app.getMessageBundle();

       final ResourceBundle rb2 = ResourceBundle.getBundle(bundleName,
                                  facesContext.getViewRoot().getLocale());

       try {

         message = rb2.getString(propertyName);

       } catch (MissingResourceException mre) {

         found = false;

       }

     }

     if (!found) {

       message = "{" + propertyName + "}";

     }

     return message;

  }

 

This is where all the hard work occurs. First, we look up the bean that maps to the logical name that was supplied. Next, we ask it for its resource bundle by calling its bean.getResourceBundleName(). This method is the only one that the bean is required to implement. Once we have the bundle name, we can look for the entry that matches the property key specified. If we don’t find one, we look for the entry in the application’s message bundle instead.

You can make this method as complex as you need. But this is sufficient for our use cases, so we’re done here.

Conclusion

The ability to roll your own variable resolvers makes it easy to create your own expression syntax. In addition, these expressions can fit into the standard mechanisms for parameter based replacement – so this can be quite powerful indeed.

In addition, all of this is transparent to the application code, and is buried within a resolver. The only requirement is that your bean should implement a method that returns its associated resource bundle’s name – not a very onerous requirement in the least. In our case, this is part of the PageBean’s interface – which supplies a dummy implementation, allowing managed beans that don’t need this support free from having to implement this method.