Problem: Required ability to have access to GWT client com.google.gwt.i18n.client.Messages files on server side(e.g. inside Servlet).
Problem reason: In my case I needed to generate the PDF representation of the web page + save some string values in DB(when someone posts comment).
Desired behaviour:
Have a single MessageResource provider that can be executed from both server side and from client side:
Solution:
- First of all make sure that all GWT message resources are generated from *.properties file via com.google.gwt.i18n.tools.I18NSync tool(or you have to synchronise i18n .properties files with GWT message interfaces all the times). Otherwise, if you just using interfaces without using *.properties files, this approach will not work.
- Now, let's educate the server how to read GWT i18n *.properties:
- I am using spring framework as server side, so in this example we will educate Spring where the GWT's i18n *.properties are stored in the app:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:messages</value>
<value>classpath:error_messages</value>
<value>classpath:/blabla/project/client/resources/i18n/messages/GlobalRes</value>
</list>
</property>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
the classpath:/blabla/project/client/resources/i18n/messages/GlobalRes is full path to GlobalRes.properties file. Make sure you include this file in classpath when yo compile your project. - Basically, after telling spring ReloadableResourceBundleMessageSource where to take the resources is pretty enough to have GWT messages being accessed on server side, by just injecting messageSource into any place on server and using messagesCourse.getMessage("messageCode",args[],locale). But this is not what we want to achieve. The aim is to have the same message resource provider that can be executed on server and on client side the same way.
- So next, let's just create this Messages Provider that can be executed on both client and on server side:
public class MessageResourceProvider {
public static GlobalRes GLOBAL_RES;
public static GlobalRes SERVER_GLOBAL_RES;
static {
try {
GLOBAL_RES = GWT.create(GlobalRes.class);
} catch (Exception e) {
}
}
public static GlobalRes getGlobalRes() {
if (GLOBAL_RES != null) {
return GLOBAL_RES;
} else {
return SERVER_GLOBAL_RES;
}
}
}
First of all, make sure that MessageResourceProvider is stored under *.client.* package, so GWT client side will be able to access it. So here we have two instances for providing resources: one for server part, the other one for client side. With client side everything is clear: we just try to call GWT.create(GlobalRes.class) in static block and if it's successfully executed(which actually means it executed on client side) then the getGlobalResources will return client version of GlobalRes.
But what about SERVER_GLOBAL_RES? Where should it come from? Obviously - it should come from server. - Ok, let's tell server part to how inject SERVER_GLOBAL_RES into MessageResourceProvider wher the server starts.
in order to do that I just added listener in my web.xml
<listener>
<listener-class>
org.kuali.lum.cmi.server.util.CmiServletListener
</listener-class>
</listener>
so application will call this listener at startup time.
the listener code is very simple:
public class CmiServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServerMessageResourceFactory serverMessageResourceFactory = (ServerMessageResourceFactory) WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean("serverMessageResourceFactory");
MessageResourceProvider.SERVER_GLOBAL_RES = serverMessageResourceFactory.getGlobalRes();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
As you see at startup time I just set MessageResourceProvider's SERVER_GLOBAL_RES which I take from serverMessageResourceFactory.getGlobalRes(). - So this is almost it, except I haven't described the most tricky and interesting part: how the serverMessageResourceFactory.getGlobalRes() will return the com.google.gwt.i18n.client.Messages resources instance that can de executed from server side and why hadn't we use messageResources, that we've created at 2nd step?:
The answer is below:
public class ServerMessageResourceFactory {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return messageSource.getMessage(method.getAnnotation(LocalizableResource.Key.class).value(), args, null);
}
};
private ReloadableResourceBundleMessageSource messageSource;
public GlobalRes getGlobalRes() {
return (GlobalRes) Proxy.newProxyInstance(GlobalRes.class.getClassLoader(), new Class[]{GlobalRes.class}, handler);
}
public void setMessageResource(ReloadableResourceBundleMessageSource messageSource) {
this.messageSource = messageSource;
}
}
The ServerMessageResourceFactory contains two functions two fields that do all the tricks:
- the getGlobalRes() returns proxy for GlobalRes which is handled by handler
- inside the handler we easily can extract the message key value(method.getAnnotation(LocalizableResource.Key.class).value()) and passed arguments(args).
- with messageSource, which already knows how to get the GWT resources from i18n *.property files we jast take all GWT client resources.
Result:
As a result now, we have MessageResourceProvider.getGolbalRes() that can be executed from both: server and client side. So you never should care wherethere you using resources from client side or from server side. Just call MessageResourceProvider.getGolbalRes() to get access to the app message resources.