messages.properties taken from db [duplicate] - java

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Design question regarding Java EE entity with multiple language support
I'm working on i18n of JSF application.
I need all standard jsf messages that are usually located in messages.properties to be taken from database. Is there some simple way to do it?
Thanks.

I think I found the answer:
public class DBMessagesBundle extends ResourceBundle {
#Override
protected String handleGetObject(String key){
...
}
#Override
public Enumeration<String> getKeys() {
...
}
}
and in FacesConfig.xml
<application>
...
<message-bundle>mypackage.DBMessagesBundle</message-bundle>
</application>
Thank You for help.

First, you will need your own MessageSource. Take a look at AbstractMessageSource and extend it:
public class CustomResourceBundleMessageSource extends AbstractMessageSource {
#Autowired
LocalizationStore localizationStore;
#Override
protected MessageFormat resolveCode(String code, Locale locale){
MessageFormat messageFormat = null;
ResourceBundle bundle = localizationStore.getBundle(locale);
try {
messageFormat = getMessageFormat(bundle, code, locale);
} catch (MissingResourceException | NullPointerException ex) {
//Return just the code in case this is used for logging, or throw, your choice
return createMessageFormat(code, locale);
}
return messageFormat;
}
protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale) throws MissingResourceException {
String msg = bundle.getString(code);
return createMessageFormat(msg, locale);
}
}
Your store must return a ResourceBundle:
This will largely be based off your db model. I would recommend using #Cachable on the getBundle() method, as your localizations are not likely to change often, and depending on your DB model, it may be expensive. The object returned needs only to implement the following methods for ResourceBundle:
public class DBResourceBundle extends ResourceBundle {
#Override
protected String handleGetObject(String key){
...
}
#Override
public Enumeration<String> getKeys() {
...
}
}
Finally, you need to register your MessageSource bean in your configuration:
<bean id="messageSource" class="com.example.CustomResourceBundleMessageSource"/>

Related

Missing functionality between Accept-Language header and ResourceBundle

Google foo failed me. I want to find out if there is a standard "by the book" way of transforming the input locales from Accept-Language header to correct ResourceBundle.
ResourceBundle::getBundle() method(s) accepts a single locale but Accept-Language can have multiple locales weighted by index, eg: de;q=1.0, sl;q=0.9.
Current code:
#Context
private HttpServletRequest request;
public String getString(String key) {
ResourceBundle i18n = ResourceBundle.getBundle("locale/strings", this.request.getLocale());
return i18n.getString(key);
}
The problem is that getLocale() returns the preferred locale, in this case de. If available resource bundles are sl and en, this will try to find de and then fallback to en, but the actual expected result by the client is sl!
My question is basically, do I have to implement a custom fallback code that iterates over HttpServletRequest.getLocales() (I don't want to reinvent the wheel..) or is there a more standard and straightforward way of doing this? I'd also settle for some 3rd party lib that fills this gap.
Custom solution so far:
#RequestScoped
public class Localization {
#Context
private HttpServletRequest request;
private ResourceBundle i18n;
#PostConstruct
void postConstruct() {
//List of locales from Accept-Language header
List<Locale> locales = Collections.list(request.getLocales());
if (locales.isEmpty()) {
//Fall back to default locale
locales.add(request.getLocale());
}
for (Locale locale : locales) {
try {
i18n = ResourceBundle.getBundle("bundles/translations", locale);
if (!languageEquals(i18n.getLocale(), locale)) {
//Default fallback detected
//The resource bundle that was returned has different language than the one requested, continue
//Only language tag is checked, no support for detecting different regions in this sample
continue;
}
break;
}
catch (MissingResourceException ignore) {
}
}
}
private boolean languageEquals(Locale first, Locale second) {
return getISO2Language(first).equalsIgnoreCase(getISO2Language(second));
}
private String languageGetISO2(Locale locale) {
String[] localeStrings = (locale.getLanguage().split("[-_]+"));
return localeStrings[0];
}
public ResourceBundle i18n() {
return this.i18n;
}
}
I would write an Interceptor, there you can set the language you want and apply the logic you want into a ThreadLocal or pass it down.
i.e you check against the available languages and define an order or set a default.
If you use Spring, you could then set LocaleContextHolder manualy or use the LocaleContextResolver instead of writing a own interceptor.

Enum Localization generic approach

Enum
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
}
In callers scattered all over the application whicg get and set the enum like below. Here are examples
Caller_1
if(employee.getStatus() == EmployeeStatus.STATUS.SUBMITTED) {
}
Caller_2
employee.setStatus(EmployeeStatus.STATUS.SUBMITTED)
Problem
I need to implement the internationalization so that end user sees the employee status as per locale. So when i set the status for french locale
it should set the value from right resource bundel. Is there a way i can achieve this without changing the caller code. Here is the solution
I can think of
My proposed solution :-
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
public static String toString() {
return I18n.getMessage("label." + this);
}
}
public final class I18n {
private I18n() {
}
private static ResourceBundle bundle;
public static String getMessage(String key) {
if(bundle == null) {
bundle = ResourceBundle.getBundle("path.to.i18n.messages");
}
return bundle.getString(LocaleContextHolder.getLocale());
}
}
With this approach I need to add toString method in every Enum without change in caller ? Is there a better generic approach spring provides ?
I am using spring 4. See if spring can help here .

How can I bring Wicket 7 to work with java.time from Java 8?

I have lots of beans and all use LocalDate and LocalDateTime. The DateTextField in Wicket and all other widgets (like the DatePicker) only work on java.util.Date. Is there any way to inject a converter into Wicket 7 so that it uses LocalDate or LocalDateTime?
The beans look like this:
public class SomeBean {
Long id = null;
LocalDate since = null;
// plus getters and setters
}
A Wicket form currently uses a CompoundPropertyModel
CompoundPropertyModel<SomeBean> model = new CompundPropertyModel<>( bean );
You can wrap your LocalDate and etc. models in a IModel<java.util.Date>, e.g.
public static class LocalDateModel implements IModel<java.util.Date> {
private IModel<LocalDate> localDateModel;
public LocalDateModel(IModel<LocalDate> localDateModel){
this.localDateModel = localDateModel;
}
#Override
public Date getObject() {
return convertLocalDateToUtilDateSomehow(localDateModel.getObject());
}
#Override
public void setObject(Date object) {
localDateModel.setObject(convertUtilDateToLocalDateSomehow(object));
}
#Override
public void detach() {
localDateModel.detach();
}
}
If you then feed models like this into the form components you want to use it should work just fine.
If you want your CompoundPropertyModel to automatically provide such wrapping models, you need to extend it and override it's CompoundPropertyModel#wrapOnInheritance(Component component) method to infer that a wrapping model is needed. Something like
#Override
public <C> IWrapModel<C> wrapOnInheritance(Component component)
{
IWrapModel<C> actualModel = super.wrapOnInheritance(component);
if (actualModel.getObject() instanceOf LocalDate) {
return new LocalDateModelButAlsoWrapping(actualModel);
} else {
return actualModel;
}
}
Where LocalDateModelButAlsoWrapping is unsurprisingly just an extension of LocalDateModel example above but which also implements IWrapModel<T>.
If you use this extension instead of your regular CompoundPropertyModel it would detect when fields are LocalDate and provide models to components (like your DateTextField) that are wrapped to look like java.util.Date models.
The code snippet I gave you is rather dirty though (you should probably not get the model object to infer its type) as I have only provided it to illustrate the general mechanism, so I suggest you devise your own way to infer the type of object expected (e.g. you can check if the Component argument is a DateTextField), but this is the general direction of the solution that I can imagine.
You can register your own converters:
https://ci.apache.org/projects/wicket/guide/7.x/guide/forms2.html#forms2_3
#Override
protected IConverterLocator newConverterLocator() {
ConverterLocator defaultLocator = new ConverterLocator();
defaultLocator.set(Pattern.class, new RegExpPatternConverter());
return defaultLocator;
}
Related: https://issues.apache.org/jira/browse/WICKET-6200
You can simply backport the converter classes from Wicket 8. You'll find these attached to this commit: https://issues.apache.org/jira/browse/WICKET-6200 (AbstractJavaTimeConverter and whatever subclasses you need for LocalDate, LocalDateTime, LocalTime, etc.)
Of course, that will not help with a DateTextField, because that has the Date type parameter hardcoded. For such, you can either create your own subclasses using the above converters, or use regular Label and TextField, with converters registered globally, as shown below:
#Override
protected IConverterLocator newConverterLocator() {
ConverterLocator converterLocator = new ConverterLocator();
converterLocator.set(LocalDateTime.class, new LocalDateTimeConverter());
converterLocator.set(LocalDate.class, new LocalDateConverter());
converterLocator.set(LocalTime.class, new LocalDateConverter());
return converterLocator;
}

Getting a list of configured actions in Struts2

In a project using Struts2 (2.3.20) I would like to run through the
configured actions (name, class, namespace, method) at application startup.
I'm using
Struts 2.3.20
struts-spring-plugin
struts-convention-plugin
For reference: I've done some work with beans and Struts injection before so not entirely fresh on this, but I'm stuck solving the problem stated here.
Any pointers on how to obtain this would be appreciated.
Further explanation
Reading Andrea's answer below I see I need to explain what I need.
I'm building a application menu builder feature for the application. My plan is to obtain the action configurations and build a tree of "menu nodes" from information in annotations on selected action classes and methods.
My problem with the code from the config-browser is that the Configuration (xwork) doesn't seem to be available outside of Struts components. Since this is an application startup task it doesn't really fit Struts' MVC component model. I'd like to put the menu building initialization in a ServletContextListener.
Fake example
Per request here is just the connection actionconfig <-> annotation <-> my_custom_menu. From this I could produce a menu structure provided from the annotations on action classes and methods.
public class ActionMenuBuilderListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent arg0) {
List<ActionCfg> actions = Struts.getConfiguredActions(); // thisi is where I'd like some help
for(ActionCfg action : actions) {
MenuAnnotation annotation = getAnnotationFromMethodOrClass(action);
if(annotation != null) {
addMenuItem(action, annotation);
}
}
}
}
Here ActionCfgis whatever class Struts would return for action configuration, Struts.getConfiguredActions() would be one or more calls to Struts components and addMenu(...) is where I add a menu item node to my structure. The structure is later the target from JSP-s to build menus.
I don't know how much more code to write.
My sollution
For completness I thought I'll include what came out of this.
First, I to plugged in into Struts through this
ServletContextListnere:
public class ActionMenuBuilderListener implements
ServletContextListener {
#Override
public void contextDestroyed(ServletContextEvent arg0) {
}
#Override
public void contextInitialized(ServletContextEvent event) {
ActionMenuDispatcherListener listener =
new ActionMenuDispatcherListener();
ServletContext context = event.getServletContext();
listener.setServletContext(context);
Dispatcher.addDispatcherListener(listener);
}
}
Then, I wrote the DispatcherListener:
public class ActionMenuDispatcherListener implements DispatcherListener {
private ServletContext servletContext;
...
#Override
public void dispatcherInitialized(Dispatcher dispatcher) {
Map<String, PackageConfig> packages = dispatcher
.getConfigurationManager().getConfiguration()
.getPackageConfigs();
Map<String, Map<String, ActionConfig>> runtimeActionConfigs = dispatcher
.getConfigurationManager().getConfiguration()
.getRuntimeConfiguration().getActionConfigs();
for (String packageKey : runtimeActionConfigs.keySet()) {
Map<String, ActionConfig> actionConfigs = runtimeActionConfigs
.get(packageKey);
for (String actionKey : actionConfigs.keySet()) {
ActionConfig actionConfig = actionConfigs.get(actionKey);
PackageConfig packageConfig = packages.get(actionConfig
.getPackageName());
if (packageConfig != null) {
String actionName = actionConfig.getName();
String namespace = packageConfig.getNamespace();
try {
ActionMenu methodAnnotation = getMethodAnnotation(actionConfig);
if (methodAnnotation != null) {
String annotationInfo = methodAnnotation.value();
log.debug("[{}, {}, {}]", namespace, actionName,
annotationInfo);
}
} catch (ClassNotFoundException e) {
log.error("{}: {}", e.getClass().getSimpleName(),
e.getMessage());
}
}
}
}
}
protected ActionMenu getMethodAnnotation(ActionConfig actionConfig)
throws ClassNotFoundException {
String className = actionConfig.getClassName();
String methodName = actionConfig.getMethodName();
Class<?> actionClass = Class.forName(className);
try {
Method method = actionClass.getDeclaredMethod(methodName, null);
ActionMenu annotation = method.getAnnotation(ActionMenu.class);
return annotation;
} catch (NoSuchMethodException | SecurityException e) {
// log.error("{}: {}", e.getClass().getSimpleName(),
// e.getMessage());
}
return null;
}
}
Just in case someone else is thinking along those line :)
First of all you need to hook into application initialization process after the configurations are loaded and parsed. One of the ways is to implement DispatcherListener which you need to add to the Dispatcher. This you can do in ServletContextListener#contextInitialized method.
The second piece of the puzzle is to get action configurations. This is pretty simple because the instance of the Dispatcher is passed as argument into dispatcherInitialized method. To get all current action configurations get RuntimeConfiguration which holds data in Map<String, Map<String, ActionConfig>>, where the first map key is package namespace, the second map key is action name and ActionConfig holds all info about action. Since you need a class name then use getClassName() method of it.
public class ActionMenuBuilderListener implements ServletContextListener,DispatcherListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
Dispatcher.addDispatcherListener(this);
}
#Override
public void dispatcherInitialized(Dispatcher du) {
Map<String, Map<String, ActionConfig>> runtimeActionConfigs = du
.getConfigurationManager().getConfiguration().getRuntimeConfiguration()
.getActionConfigs();
}
// other methods
}
And of course don't forget to register your listener in web.xml.
You are free of building this thing for your personal growth, but beware that it already exist.
It is called Config Browser Plugin (struts2-config-browser-plugin-2.3.20.jar).
It is included by default with the Maven archetypes, and you must remember of removing it before going in production.
Once imported it is available at the URL:
//www.SERVER_NAME.com:8080/WEBAPP_NAME/config-browser/actionNames
It gives you the exact informations you are looking for: actions, methods, results, parameters, mappings etc. and it looks like this:

How to override some Resources from a PropertyResourceBundle

I have an exsisting Java/JSF website all the text on the screen is coming from property files via <f:loadBundle basename="org.example.applicaltion" var="applicaltion" /> which pulls the text from applicaltion.properties.
For a runtime configurable subset of these I am wanting to pull the string from else where (CMS via web services). Looking at the ResourceBundle class it appares to have an infrastructer for something close to this with delegation to a parent ResourceBundle.
I am wanting somethis like this
public class Applicaltion extends ResourceBundle{
#Override
protected Object handleGetObject(String key) {
if(overridenKey(key)){
return overridedValue(key);
}
return null; // ResourceBundle.getObject will delegate to parent
// if we return null
}
}
I have tried this and parent is null, I assume this is more used for the case of default -> en -> en_GB.
I am considering the not very appealing option of have the property file a different name from the custom resourceBundle and then delegating through the full stack of ResourceBundle.getBundle(PROPERTY_FILE_NAME).getString(key) from within CustomResourceBundle.handleGetObject(key).
Any better ideas?
I ended up solving this by checking if we had an override value, if we did returning that, else delegating to the standard resource bundle
public class UILabels extends ResourceBundle {
private ResourceBundle getFileResources(){
return ResourceBundle.getBundle("com.example.web.UILabelsFile", this.getLocale());
}
public Enumeration<String> getKeys() {
return getFileResources().getKeys();
}
protected Object handleGetObject(String key) {
if(overrideValue(key)){
return getOverridenValue(key);
}
return getFileResources().getObject(key);
}
}
Note the slight difference in name class is UILabels which is what all clients will use the file is UILabelsFile so the ResourceBundle loader does not go recursive.
You could write a custom PropertyResolver, and then have that perform the logic of where to pull the property values from.
For example, you could define a bean called messageSource and have that load up application.properties, plus your CMS properties or whatever you have.
Then write a custom PropertyResolver (there's an example of how to do this with Spring's MessageSource here) and link it in to your faces-config.xml using something like this:
<application>
<property-resolver>
com.package.MessageSourcePropertyResolver
</property-resolver>
</application>

Categories