In part 1, we created a simple application that made use of string resource bundles in JSF and in part 2 we extended it by using CDI to inject the resource provider into beans so we can re-use our code for accessing locale specific string based resources.

One benefit of using CDI to inject your beans instead of just creating them manually is the ability to utilize the event mechanism within those beans. In this example, we are going to fire an event when we discover a resource is missing.

  1. Start by creating a MissingResourceInfo class that stores the info regarding the missing resource (key and locale). This will be passed along with the event so the observer has all the info when it receives the event.
    public class MissingResourceInfo {
    
    	private final String key;
    	private final String locale;
    
    	public MissingResourceInfo(String key, Locale locale) {
    		this(key, locale.getDisplayName());
    	}
    
    	public MissingResourceInfo(String key, String locale) {
    		super();
    		this.key = key;
    		this.locale = locale;
    	}
    
    	public String getKey() {
    		return key;
    	}
    
    	public String getLocale() {
    		return locale;
    	}
    }
    
  2. We fire an event when we discover a resource key is missing from the locale specific properties file being used. In the MessageProvider class from part 2, we inject the Event instance in to a field and modify the getValue method to fire off the event when a key is missing.
    @RequestScoped
    public class MessageProvider {
    
    	@Inject
    	private Event<MissingResourceInfo> event;
    	....
    	....
    	public String getValue(String key) {
    		String result = null;
    		try {
    			result = getBundle().getString(key);
    		} catch (MissingResourceException e) {			
    			result = "???" + key + "??? not found";
    			event.fire(new MissingResourceInfo(key, getBundle().getLocale()));
    		}
    		return result;
    	}
    }
    
  3. Finally, we want to add an observer for that event that handles it when it is fired. To do this, we will add a new class for this purpose.
    public class MissingResourceObserver {
    
    	public void observeMissingResources(@Observes MissingResourceInfo info) {
    		String template = "Oh noes! %s key is missing in locale %s";
    		String msg = String.format(template, info.getKey(), info.getLocale());
    		System.out.println(msg);
    	}
    
    }
    
  4. Finally, to see it in action, we need to add bean getter to fetch a non-existing value from the bundle.
    @Named
    @RequestScoped
    public class AnotherBean {
    
    	public String getMissingResource() {
    		return provider.getValue("ImNotHere");
    	}
    }
    
  5. We can see this in action by adding to our JSF page a call to this property
    #{anotherBean.missingResource}
    

    Results in the following being displayed in the console :

    Oh noes! 'ImNotHere' key is missing from locale English (United States)
    

Remember, you can have as many event observers as you want so you can have one that just logs it to the console or one that emails if missing text strings are urgent (i.e. production). You could have it write the missing values to the database so during development cycles, the missing strings can be exported and put into the properties files. This is especially useful if you have third parties handling the translation where you can send them a list of missing items to translate in one big batch.

What about EL?

This works great when we are accessing resource bundles from Java, but what about when we are using EL expressions to access the values. JSF channels those expressions straight to the resource bundle without the ability to intercept them. One way to fix this would be to write our own EL expression resolver and see if the root of the expression maps to a resource bundle and if so try to obtain the value from the bundle and fire the event if the item is not found. This would work, but it seems overkill since every EL expression would end up going through the resolver.
Remember that the syntax for accessing resource strings in EL is #{bundlename.key}, and Java Map classes can also be accessed using the same syntax. What we can do is create a named bean that (barely) implements the map interface and in the get method, it gets the value from the injected resource bundle. This means EL expressions will use our Map bean and we can delegate the call to the Message provider which will handle missing expressions.

  1. Start by creating a new bean that implements the Map interface. Most of the methods will throw exceptions that they are not implemented, we only really care about the method to get values.
    @Named("messages")
    @RequestScoped
    public class ResourceMap implements Map<String, String> {
    
    	@Inject
    	private MessageProvider provider;
    
    	@Override
    	public int size() {
    		return 0;
    	}
    
    	@Override
    	public boolean isEmpty() {
    		return false;
    	}
    
    	@Override
    	public boolean containsKey(Object key) {
    		return provider.getBundle().containsKey((String) key);
    	}
    
    	@Override
    	public boolean containsValue(Object value) {
    		throw new RuntimeException("Not implemented");
    	}
    
    	@Override
    	public String get(Object key) {
    		return provider.getValue((String) key);
    	}
    
    	@Override
    	public String put(String key, String value) {
    		throw new RuntimeException("Not implemented");
    	}
    
    	@Override
    	public String remove(Object key) {
    		throw new RuntimeException("Not implemented");
    	}
    
           ....
           ....
           ....
    }
    

    If the interface method isn’t listed, assume it throws a “not implemented” exception. We inject the MessageProvider instance so we can re-use our message provider bean to fetch messages and fire the events.

  2. We gave the bean the name messages so if we go to our web page and add the following to our page :

    First name from map = #{messages.firstName}<br/>
    Missing name from map = #{messages.missingValue}<br/>
    

    When we run our application and refresh the page, we get the value displayed for messages.firstName, and a missing value for messages.missingValue. In our server log we get

    Oh noes! missingValue key is missing in locale English (United States)
    

    Since we re-used the MessageProvider bean, it fires the event when it cannot find a key value so even though we called it from the JSF page, it ended up being routed through to our message provider and the event being fired.

How you use this is up to you, but since the syntax is the same whether you use the Map or the JSF resource variable, you can switch between the two depending on whether it is a development or production environment. You may also use different event handlers depending on the deployment environment. Alternatively, you may be worried about performance in production, or the fact that you lose autocompletion if you use the map in development.
To switch implementations, just give either the JSF resource variable, in faces-config or the MessageProvider bean the name used for your resource string component. You can switch components without having to change either your code or your JSF pages.

Download the Maven source code for this project and run it locally by typing unzipping it and running mvn clean jetty:run in the command line, and going to http://localhost:8080/resourcedemo/.