Sep 24 2009

Client side Internationalization in struts application with javascript

Struts gives a straightforward way to internationalize your application. Using properties files, you can display different messages to users with different locales. For details on how to internationalize your application, view the struts 1.x site. However, this kind of internationalization works best only for non-dynamic sites. For even slightly dynamic sites, where you have a client side javascipt validation, you would run into a problem.

The problem with javascipt is that you typically cannot pick up messages from a resource bundle (from the server) depending on user’s locale. I would discuss here how to get the properties file to client side (javascript) in a usable form. The idea is, we convert the properties file as a JSON which is readily available for use in javascript.

Suppose we have a file named MessageResources.properties for the default locale (EN). And another file named MessageResources_de.properties for the german locale. Struts i18n will take care of the internationalization in the JSP side, but for javascript, we need to make the properties available on the outputted HTML as a javascript variable.

Here is a sample code which accomplishes the same in struts action class:


import java.util.Enumeration;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;

public class ActionInitializer extends Action{
	public void execute(HttpServletRequest request, HttpServletResponse response){
		Locale locale= request.getLocale();
		ResourceBundle resourceBundle = (PropertyResourceBundle) ResourceBundle.getBundle("MessageResources",locale);
		StringBuilder resourceJson = new StringBuilder();
		resourceJson.append("{");
		Enumeration<String>keys=resourceBundle.getKeys();
		while(keys.hasMoreElements()) {
			resourceJson.append("\"");
			String key=keys.nextElement();
			String value=resourceBundle.getString(key);
			resourceJson.append(key);
			resourceJson.append("\":\"");
			resourceJson.append(value);
			resourceJson.append("\",\n");
		}
		resourceJson.append("\"complete\":\"completed json text\"");
		resourceJson.append("}");
		request.setAttribute("resourceJson", resourceJson);
                //
                //Your business logic goes here
                //
                return mapping.findForward("success");
	}
}

Couple of things to note, don’t forget to escape the double quotes, and don’t finish your JSON with a comma (which is why I have added a dummy element – ‘complete’) otherwise the generated JSON will throw a javascript error in IE6.

The above code will generate a JSON object which is stored as an attribute, accessible in the JSP. This sample code illustrates how to use this JSON to store the properties

<script type="text/javascript">
         var resourceJson=eval(${requestScope.resourceJson});
         alert(resourceJson["complete"]);
</script>

Note that in the first line, var resourceJson=eval(${requestScope.resourceJson});, the el is evaluated at the server side, so the outputted HTML will look something like this:

<script type="text/javascript">
         var resourceJson=eval({
              "text.hello":"Willkommen",
              "text.contact":"Kontact",
              "complete":"complete"});
         alert(resourceJson["complete"]);
</script>

The ‘alert’ is only for demonstrating how you can access your locale specific message from the JSON. Happy i18n :)


Sep 14 2009

SWFUpload using jsp/struts/java

Swfupload is a great tool for uploading files to your server if you want to use a Flash uploader instead of the default HTML file uploader. It’s a small Javascript/Flash library which gives you a lot of options to customize the upload, a file progress bar being the most useful. Although a great tool, but it works best with php/asp, and not java/struts. The developers of this tool are definitely not java guys either.

Anyway, I tried implementing an swfupload upload for my struts web application, and faced a lot of problems which are outlined:

1) Swfupload won’t send the cookies (it’s a flash problem rather than swfupload’s), so my session could not be validated on the server side. I was using firefox/safari on mac. Doesn’t really matter because it was Flash who was sending the request and not the browser itself. Use firebug, and verify for yourself :)

2) “post_params” would not be sent along with the request. So any hope of capturing the form data were gone.

So I decided to solve the problem myself, and here is the solution:

For the cookies not being sent to the server, I had to send the JSESSIONID along with the URL. Simple enough, in the JSP, just send

JSESSIONID:"<%=session.getId()%>"

. But on the server side, getting a JSESSIONID is not good enough to get the session, because getSessionContext() is deprecated, and returned a null session

 request.getSession().getSessionContext().getSession
    (request.getParameter("JSESSIONID"));

So, I decided to manage these sessions myself, so that any user session could be obtained just by using the “JSESSIONID”. For this, I had to add a session listener to the webapp which actively monitors the user sessions, and stores the session in an application level hashmap. Doing this is straightforward, go to your web.xml and add the following code:

<listener>
 <listener-class>
 com.anujrathi.pc.filters.SessionListener
 </listener-class>
 </listener>

And add this class implementing HttpSessionListener:

package com.anujrathi.pc.filters;

import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SessionListener implements HttpSessionListener{
 Log log = LogFactory.getLog(SessionListener.class);

 public void init(ServletConfig config){
 log.info("initialed sessionlistener");
 }

 /**
 * Adds sessions to the context scoped HashMap when they begin.
 */
 public void sessionCreated(HttpSessionEvent event){
 HttpSession session = event.getSession();
 ServletContext context = session.getServletContext();
 HashMap<String, HttpSession> users =  (HashMap)context.getAttribute("users");
 users.put(session.getId(), session);
 log.info("setsession: "+session.getId());
 context.setAttribute("users", users);
 }

 /**
 * Removes sessions from the context scoped HashMap when they expire
 * or are invalidated.
 */
 public void sessionDestroyed(HttpSessionEvent event){
 HttpSession    session = event.getSession();
 ServletContext context = session.getServletContext();
 HashMap users = (HashMap)context.getAttribute("users");
 users.remove(session.getId());
 log.info("removed session: "+session.getId());
 }

}

Go to next page to view the rest of the solution: