This week's book giveaway is in the OO, Patterns, UML and Refactoring forum. We're giving away four copies of Refactoring for Software Design Smells: Managing Technical Debt and have Girish Suryanarayana, Ganesh Samarthyam & Tushar Sharma on-line! See this thread for details.
How do I provide for a message bundle in JSF 2 with both overrides and fall-through?
I have been tasked with updating a good-sized JSF R1 1.1 application to JSF 2, and one of the requirements is to bring everything back into the JSF model as much as possible. Our current app does horrible things to the sun and apache JSF API's and implementations, but considering the horrible things that those implementations did to us, I'm going to call it fair.
One of the things that was done which was actually somewhat clever, was providing a mechanism to override message bundles, and I'd like to reproduce that in as nice a way as possible. That is, I don't want to have to rip the innards out of the JSF implementation jar. Let me explain the mechanism, so you can see what I'm asking:
We have a message bundle, messages.properties, assigned to the var "msgs", just like you'd expect. We also have a file called "messages-override.properties" which provides anywhere from 0 to all redefinitions of the messages.properties file key-value pairs. By overriding the f:messageBundle tag with our own implementation (and the message bundle class, and etc, etc from there into framework voodoo where comments like 'copied this class entirely from the sun implementation so I could make changes' run wild), we were able to search and find this file, and perform the overrides. Why all the trouble? Why not just rewrite the messages.properties file? Well, our app can be customized by an individual customer by altering the messages-overrides.properties file, while any updates we provide via patches will only update the messages.properties file - thus, they don't have to re-apply their customizations every patch. So that's the override.
Where does the fall-through come into play? Well, we're starting to get requests for multilanguage support, and the initial customers, at least, are prepared to provide their own translations. However, there are several parts of the app - administrator, employee, and end user usage, and we only need to provide the end users with translations. In order to avoid needless duplication (not to mention knowing which new message bundles to provide in each patch - plus an override file?) - we'd like it to fall through to the default (english) language version if it can't find a ~specified language~ variant.
So, worst case, foreign-language-override->foreign-language->default (english) override->default (english) language.
We actually don't perform a staggered lookup currently. We just apply these rules once, when the property files are loaded to generate the end map.
So, to repeat the question: How should I go about implementing message bundles with overrides and fall through in a way that doesn't require that deep voodoo. Is it possible? In the end, the functionality is what is important, not how it's implemented.
I don't think that there's anything really JSF-specific in there. Ironically, there's so much detail in your explanation, I'm not sure if I read it all right, but basically, a MessageBundle is a chained set of collections, so when a key is presented, the chain is searched, starting with the most-specific collection and working back until the final default property resource. Adding user-customizable messages is simply a matter of putting them in at the head of the chain, then.
While I haven't worked with the details lately, I know that a java.lang.Properties resource has a constructor that allows the next property resource as a constructor parameter, which is how that particular class does it. I know something similar exists in ResourceBundles, because I did do it, but after about 7 years, I forget the details.
The only actual tricky part is that if the user-supplied messages are dynamically updateable, you cannot (safely!) store them as resources in the WAR, so you'd want to provide an external directory (or, if you prefer, database table, LDAP directory, etc. to load the properties from.
But in the end, it's all fairly simple and straightforward.
An IDE is no substitute for an Intelligent Developer.
Joined: Jul 20, 2005
I tried to play around with chaining the ResourceBundles, which unfortunately led me to recreating much of the logic that the servlet container was supposed to deal with when it comes to internationalization. In the end, I backed up out of the ResourceBundle.control manipulation, as it was quickly becoming overly complex. What I did do was this:
1) Wrote a utility method to retrieve a given string using the standard MessageBundle.getBundle() calls (which the docs say are nicely cached for me) - including a special check for the existence of the override files (locale-specific items were already taken care of).
2) Define the resource bundle in the faces-config.xml file, and point the base-name attribute at a class that extends ResourceBundle 3) Produce a unique ID for that given class to identify which property file(s) it needs to load.
4) Override the getKeys() and handleGetObject() methods in the extended class to call my utility method.
(technically I created an abstract class in step 2, and extend it for each resource/property file I need to read)
The only thing that strikes me as inelegant is that I'm forced to make a separate class file that's little more than a hard coded identifier for a given property file. In the end I can deal with that; each new class is only a single new line of code in the constructor, and taken as a whole it's only about 40 lines of code - and it works.