Using Multiple Resource bundles in JSF - spring

I am trying to access multiple resource bundles from a JSF page. I have two resource bundles:
general_messages.properties
module_message.properties
I want to access both these resource bundles in a JSF file. One way I can do this is to define specific properties for each of these bundles:
<f:loadBundle basename="com.sample.general_messages" var="general"/>
<f:loadBundle basename="com.sample.module_message" var="module"/>
Is there a way I can access both these resource bundles using the same variable name.
Something like:
<f:loadBundle basename="com.sample.general_messages, com.sample.module_message" var="general"/>
Or any other best way to access multiple resource bundles?

You tagged your question with Spring, so I recommend you using Spring MessageSource. Spring MessageSource can aggregate many property files even hierarchically. It gives you many advantages over old java ResourceBundle.
You can define spring MessageSource in you spring-config.xml like this:
<!--
Application messages configuration.
-->
<bean id="messageSource" name="resourceBundle"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:fallbackToSystemLocale="false"
p:cacheSeconds="0">
<property name="basenames">
<list>
<value>/messages/Messages</value>
<!-- <value>${application.messages}</value>-->
</list>
</property>
</bean>
Than you can define your Class which extends ResourceBundle like this (Needs some cleaning and refactoring):
public class SpringResourceBundle extends ResourceBundle
{
private MessageSource messages;
private FacesContext fc;
private Locale locale = null;
public SpringResourceBundle()
{
fc = FacesContext.getCurrentInstance();
WebApplicationContext webAppCtx = (WebApplicationContext) fc.getExternalContext().getApplicationMap().get(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
messages = (MessageSource) webAppCtx.getBean("messageSource");
}
#Override
public Locale getLocale()
{
Locale loc = fc.getELContext().getLocale();
if (fc.getExternalContext() != null) {
loc = fc.getExternalContext().getRequestLocale();
}
try {
UIViewRoot viewRoot = fc.getViewRoot();
if (viewRoot != null) {
loc = viewRoot.getLocale();
}
if (loc == null) {
loc = fc.getApplication().getDefaultLocale();
}
} catch (Throwable th) {
System.out.println(th.getMessage());
loc = locale;
}
locale = loc;
return loc;
}
#Override
protected Object handleGetObject(String key)
{
try {
return messages.getMessage(key, null, getLocale());
} catch (NoSuchMessageException e) {
return "???" + key + "???";
}
}
#Override
public Enumeration<String> getKeys()
{
return Collections.enumeration(Collections.EMPTY_LIST);
}
}
Finnaly in faces-config.xml declare your resource bundle with Class above. Something like this:
<application>
<locale-config>
<default-locale>en</default-locale>
<supported-locale>cs</supported-locale>
<supported-locale>de</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<message-bundle>your.package.SpringResourceBundle</message-bundle>
</application>
Here you go Spring MessageSource in JSF. Hope it's understandable.

If two resource bundles contain the same key , then which resource bundles should be used to resolve this key ? So ,IMO ,I don't think the same variable name can be assigned to multiple resource bundles.
Perhaps , you can merge all the .properties into a single .properties in your build process (make sure all keys in the merged properties file is unique , for example , by adding some prefix in each key.) . Then you use this single merged .properties throughout the application.

The only situation (that I know of) in which JSF checks multiple files for the same bundle is if you are providing bundles for multiple locales (see Providing Localized Messages and Labels).
You might be able to point the f:loadBundle tag to a class that extends ResourceBundle instead of a properties file and use that class to reference multiple properties files. I haven't tried that before though.
Also, if you are using Seam, it provides the ability to register multiple 'global' bundles as well as bundles that can be associated with one of more views (facelets), all of which can be referenced using messages e.g. #{messages.my_message} as described here (that's for Seam 2, it may be a little different in Seam 3). I think that's what you're after though.

Related

Is it possible to have application.properties depend on multiple profiles?

Is it possible to have a Spring Boot properties file depend on two or more profiles? Something like application-profile1-profile2.properties?
Spring Boot does not support this out of the box. It only supports a single profile as described here.
However, it does provide enough flexibility to add your own property sources using EnvironmentPostProcessor.
Here is an example of how to implement this:
public class MultiProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String[] activeProfiles = environment.getActiveProfiles();
for (int i = 2; i <= activeProfiles.length; i++) {
Generator.combination(activeProfiles).simple(i)
.forEach(profileCombination -> {
String propertySourceName = String.join("-", profileCombination);
String location = "classpath:/application-" + propertySourceName + ".properties";
if (resourceLoader.getResource(location).exists()) {
try {
environment.getPropertySources().addFirst(new ResourcePropertySource(propertySourceName, location));
} catch (IOException e) {
throw new RuntimeException("could not add property source '" + propertySourceName + "'", e);
}
}
});
}
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Couple of things to note:
This implementation only supports .properties files but can easily be extended to .yml files as well.
getActiveProfiles already returns the profiles in an order where the last one wins. This implementation relies on this order and builds the different file names leveraging this order. i.e. if active profiles are: profile1,profile2,profile3 then application-profile1-profile3.properties is supported but application-profile3-profile1.properties isn't, and application-profile1-profile3.properties will override properties defined in application-profile1.properties or application-profile3.properties.
This implementation uses a third party library com.github.dpaukov:combinatoricslib3 to create the different sets of profiles.
The property sources are added to the front of the property source list to override existing sources. But if you have custom property sources that should take precedence you need to modify this a bit to consider them in the order, i.e. by leveraging methods like Environment.addAfter.
Registering an EnvironmentPostProcessor is done using the spring.factories file.
There are 4 ways I know.
insert .yaml or .properties programmatically like Asi Bross Said. Use ResourceLoader or YamlPropertySourceLoader to insert.
Use .yaml. but it will be replace when you have a another spring project to dependent it.
Use properties instead of profiles. (For api project)
Use one #PropertySource to define properties file A.
Get the variables from properties file A and assign them to the parameters in another #PropertySource file path expression.
For example:
resources
/-application.properties <-- remove or empty,because it will be override by application project
/-moduleA
/-application.properties <-- Intellij can identify properties files started with application-
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/moduleA/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
Content of Java Config file:
#SpringBootApplication
#PropertySources({
#PropertySource("/moduleA/application.properties"),
#PropertySource("/moduleA/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties"),
})
public class ModuleConfig {}
Use properties instead of profiles. (For application project)
resources
/-application.properties
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
The content of SpringMvcApplication.java:
#SpringBootApplication
#PropertySource("/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties")
public class SpringMvcApplication {...}

mybatis 3.1 + sprinng 3.2 properties

We have 4 applications running on a Tomcat7 server. The existing applications work on Hibernate and Spring.
The backend is connected to a second database and some old schemas are kept here live. Each schema is called xxx_live and xxx_test.
When the Tomcat server starts, a JNDI property is set for the right environment.
Test
Local
Live
The properties are parsed on an extention of the PropertySourcesPlaceholderConfigurer class:
public class GenericPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
private String application;
private String environment;
private static final String ENVIRONMENT = "environment";
public GenericPropertySourcesPlaceholderConfigurer(String application) throws IOException {
this.application = application;
this.environment = System.getProperty(ENVIRONMENT);
if (this.environment == null) {
this.environment = System.getenv().get(ENVIRONMENT);
}
initPropertySources();
}
/**
* setup default properties configuration
* Default configuration properties look like :
* app-global.properties
* app-environment.properties
* jndi properties
*/
private void initPropertySources() throws IOException {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new ResourcePropertySource(new ClassPathResource(MessageFormat.format("properties/{0}-global.properties", application))));
propertySources.addLast(new ResourcePropertySource(new ClassPathResource(MessageFormat.format("properties/{0}/{1}.properties", environment, application))));
propertySources.addLast(new NotFailingJndiPropertySource("jndi"));
setPropertySources(propertySources);
setIgnoreResourceNotFound(false);
setIgnoreUnresolvablePlaceholders(true);
}
}
Now we're migrating everything to MyBatis. Is there a way to inject or parse these properties into my XML configuration?
Something like:
<select id="findAllUsers" parameterType="list" resultType="user">
SELECT * FROM ${mybatis.default_schema}.USER
</select>
Yes, Definitely you can pass this property.
The function declaration in DAO layer (JAVA Mapper for mybatis in spring) would be like
List<User> findAllUsers(#Param("schemaName") String schemaName)
And when you call this function pass the schema name as argument.
Few Suggestions (Assuming you are new to MyBatis)
You should rather configure your Property using spring's util tag in context.xml
i.e. <util:properties id="mailProps" location="classpath:mail.properties" />
Scan for Mappers & Autowire using spring (again in context.xml)
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.foo.bar" />
</bean>
Where com.foo.bar is package where you Java Interface's representing your XML are located.
This way You will actually be using spring's benefits i.e. DI / IOC
parameterType would be String or java.lang.String not list.
If you need further help / any doubts feel free to ask back.
HTH
Thanks.

Escape property reference in Spring property file

I want to escape my Spring propeties file in order to get in my bean property: ${ROOTPATH}/relativePath
I have a simple Spring config file that contains:
<context:property-placeholder location="classpath:myprops.properties" />
<bean id="myBean" class="spring.MyBean">
<property name="myProperty" value="${myproperty}" />
</bean>
The myprops.properties contains:
myproperty=\${ROOTPATH}/relativePath
The above setup returns: Could not resolve placeholder 'ROOTPATH'. I tried a lot of possible syntaxes but was not able to find the right one.
Instead of ${myproperty} use #{'$'}{myproperty}. Simply replace $ with #{'$'}.
Seems so far, that is no way to escape the ${}, however you can try below configuration to solve the problem
dollar=$
myproperty=${dollar}{myproperty}
Result for myproperty will be ${myproperty} after evaluation.
Here is a Spring ticket which asks for escaping support (still unresolved at the time of writing).
The workaround of using
$=$
myproperty=${$}{ROOTPATH}/relativePath
does provide a solution, but looks quite dirty.
Using SPEL expressions like #{'$'} did not work for me with Spring Boot 1.5.7.
Although it works, escaping the placeholder is super-ugly.
I achieved this my overriding PropertySourcesPlaceholderConfigurer.doProcessProperties and using a custom StringValueResolver
public static class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
#Override
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
StringValueResolver customValueResolver = strVal -> {
if(strVal.startsWith("${something.")) {
PropertySourcesPropertyResolver customPropertySourcesPropertyResolver = new PropertySourcesPropertyResolver(this.getAppliedPropertySources());
String resolvedText = customPropertySourcesPropertyResolver.resolvePlaceholders(strVal);
//remove the below check if you are okay with the property not being present (i.e remove if the property is optional)
if(resolvedText.equals(strVal)) {
throw new RuntimeException("placeholder " + strVal + " not found");
}
return resolvedText;
}
else {
//default behaviour
return valueResolver.resolveStringValue(strVal);
}
};
super.doProcessProperties(beanFactoryToProcess, customValueResolver);
}
}
plugging it into the app
#Configuration
public class PlaceHolderResolverConfig
{
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer placeHolderConfigurer = new CustomPropertySourcesPlaceholderConfigurer();
placeHolderConfigurer.setLocation(new ClassPathResource("application.properties"));
return placeHolderConfigurer;
}
}
In the above example, for all properties starting with something.* nested placeholders wont be resolved..
remove the if(strVal.startsWith("${something.")) check if you want the behaviour for all properties

Proper way to pass config properties loaded by Spring to JSF world

I have a duplicated configuration file under WEB-INF directory, called configDEV.properties and configPRO.properties (one for development environment, and the other for production environment).
I load the proper file thanks to these Spring declaration and this Tomcat launch parameter:
<context:property-placeholder
location="WEB-INF/config${project.environment}.properties" />
-Dproject.environment=PRO
(or –Dproject.environment=DEV)
Then, in a servlet listener (called StartListener) I do the following, in order to allow JSF to access these properties, in the managed beans, and in the jsp views. (In concrete, we are going to play with a property called cfg.skin.richSelector).
public class StartListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
//Environment properties
Map<String, String> vblesEntorno = System.getenv();
//Project properties
String entorno = vblesEntorno.get("project.environment");
String ficheroPropiedades = "/WEB-INF/config" + entorno + ".properties";
try {
Properties props = new Properties();
props.load(sc.getResourceAsStream(ficheroPropiedades));
setSkinRichSelector(sc, props.getProperty("cfg.skin.richSelector"));
} catch (Exception e) {
//...
}
}
private void setSkinRichSelector(ServletContext sc, String skinRichSelector) {
sc.setInitParameter("cfg.skin.richSelector", skinRichSelector);
}
public void contextDestroyed(ServletContextEvent sce) {}
}
In a JSF managed bean:
public class ThemeSwitcher implements Serializable {
private boolean richSelector;
public ThemeSwitcher() {
richSelector = Boolean.parseBoolean(
FacesContext.getCurrentInstance().getExternalContext().getInitParameter("cfg.skin.richSelector"));
if (richSelector) {
//do A
} else {
//do B
}
}
//getters & setters
}
In a xhtml page:
<c:choose>
<c:when test="#{themeSwitcher.richSelector}">
<ui:include src="/app/comun/includes/themeSwitcherRich.xhtml"/>
</c:when>
<c:otherwise>
<ui:include src="/app/comun/includes/themeSwitcher.xhtml"/>
</c:otherwise>
</c:choose>
All of this WORKS OK, but I want to ask the experts if it is the most suitable way to do that, or if this could be simplified in some way???
Thanks in advance for your hints and advices
It depends on which version of Spring you are using. If it happens to be newest Spring 3.1, you can take advantage of #Profile:
Springsource Blog
Springsource reference
Leave the property-placeholder in your applicationContext.xml if you are using it in your spring beans.
Configure a context init param in your web.xml like this:
<context-param>
<param-name>envProp</param-name>
<param-value>${project.environment}</param-value>
</context-param>
Also move the properties file under some package in your classpath (Note: Due to this change you need to locate the resource in the application context using classpath*: prefix)
and then you can load the bundle in your JSF page like this:
<f:loadBundle basename="com.examples.config#{initParam['envProp']}" var="msgs"/>
and use something like this:
<h:outputText value="#{msgs.cfg.skin.richSelector}" />
But instead of setting the system property like this configure ProjectStage via JNDI as mentioned by Ryan Lubke in his blog so that you can use the same property even for javax.faces.PROJECT_STAGE context parameter.

Internationalized drop downs using Spring 3

Story
I have a select control that represents user access level. I'm looking for a way to internationalize it. The label should be loaded from a message resource and the value should be used as is. I prepare all my drop down lists in controllers using a simple SelectOption class that has a label and a value properties. This way, my select's look consistent accross all jsp's.
Problem
I've found some examples but they are based on logic within jsp. Developer loops through his labels and manually constructs the option tag using a message resource. While this works, there just has to be a better way. I've also found some comments that Spring 3 will have support for internationalizing option labels but I can't find anything concrete on that.
Controller logic
Collection<SelectOption> optionList = new ArrayList<SelectOption>();
optionList.add(new SelectOption("-SELECT-", "-"));
optionList.add(new SelectOption("Administrator", "ADMIN"));
optionList.add(new SelectOption("Editor", "EDIT"));
bean.setFilterUserAccessLevelOptionList(optionList);
JSP logic
<form:select path="filterUserAccessLevel" items="${bean.filterUserAccessLevelOptionList}" itemLabel="label" itemValue="value"/>
Questions
I would like to add options in my controller in this way: optionList.add(new SelectOption("userAccessLevelAdministratorLabel", "ADMIN")); and have Spring convert userAccessLevelAdministratorLabel to a value from a message resource. Is this possible?
If Spring 3 cannot do this for me, how else can this be achieved without manually constructing the option tag within jsp?
=== 2012-01-15 ==============================================================
Still trying to work out a solution using aweigold's idea.
Controller
#Controller
public class UserController {
#Autowired
private UserService userService;
#Autowired
SelectOptionListBuilder listBuilder;
#RequestMapping("/userIndex/{pageNumber}")
public ModelAndView getUserList(#PathVariable Integer pageNumber, #ModelAttribute("userIndexBean") UserIndexBean phantomBean, Locale locale, Model model) {
UserIndexBean bean = new UserIndexBean();
// prepare filter form
Collection<SelectOption> optionList = listBuilder.getUserAccessLevelOptionList(true, SortOrder.NONE, locale);
bean.setFilterUserAccessLevelOptionList(optionList);
SelectOptionListBuilderImpl
#Component
public class SelectOptionListBuilderImpl implements SelectOptionListBuilder, MessageSourceAware {
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
#Override
public List<SelectOption> getUserAccessLevelOptionList(boolean addSelectPrompt, SortOrder sortOrder, Locale locale) {
List<SelectOption> optionList = new ArrayList<SelectOption>();
if(addSelectPrompt) {
optionList.add(new SelectOption(messageSource.getMessage("common.selectPromptLabel", null, locale), "-"));
}
messageSource mapping
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/i18n/messages" />
<property name="defaultEncoding" value="UTF-8"/>
<property name="UseCodeAsDefaultMessage" value="true"/>
</bean>
Exception
org.springframework.context.NoSuchMessageException: No message found under code 'common.selectPromptLabel' for locale 'en_CA'
When I need to do operations like this in a Controller outside of a jsp, I've been making my Controllers MessageSourceAware. Spring will then inject a new MessageSource when they are swapped, and you can interrogate it much like Spring does. In your example, you would do something like this:
#Controller
public class someController implements MessageSourceAware {
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
#RequestMapping
// Pass in the locale from the LocaleResolver
public void someMapping(Locale locale){
optionList.add(new SelectOption(
messageSource.getMessage("userAccessLevelAdministratorLabel", null, locale),
"ADMIN"))
}
}
Have a look at a spring roo project. They managed this kind of problem by creating tagx tags. This tags do what you already descibed (it contains a litte logic to load the messages from ressources and build the option tags). But because the logic is witten once and you can use this tags like normal tags in you jspx files, it feels like a tag that do what you want to have.

Resources