Search This Blog

Tuesday, August 2, 2011

WebSphere Portal 6.x Locale Resolution

When developing applications for WebSphere Portal there will be some point in time that the application should support I18N. For every language an application supports a resource bundle is provided. If a given language is not supported the application falls back to its default resource bundle. To format currencies and dates the JSTL formatting tags can be used. Based on the locale selection process of WebSphere Portal, the translation of dates and currencies in the correct locale may not what be exactly what you expect/want. This article provides a portal wide solution to handle locales in a consistent and transparent way.

Language selection process
WebSphere Portal server 6.x uses the following language selection process taken from the portal documentation:

  1. The language encoded in the URL takes highest priority. The portal does not encode a locale into the URL by default.
  2. If the user has logged in, the portal displays in the preferred language selected and stored in the user repository by the user.
  3. If no preferred user language can be found, the portal looks for the language defined in the user's browser. If the portal supports that language, it displays the content in that language. If the browser has more than one language defined, the portal uses the first language in the list to display the content.
  4. If no browser language can be found, for example if the browser used does not send a language, the portal resorts to its own default language.
  5. If the user has a portlet that does not support the language that was determined by the previous steps, that portlet is shown in its own default language.

By looking at step 3, the portal should accept the browser language if it supports this language. The same is true for the portlet itself. Unfortunately, it is not clear how the portal determines if a given language is supported or not. The documentation is not clear about this either.

By executing extensive tests (removing languages with XML access and removing the resource bundles of those specific language from portal) with different locales sent from the browser, the portal always took the language from the browser if the user did not have a specific language set in its user profile. This is not an issue with the resource bundles since the application falls back to its default resource bundle if the language is not supported. The JSTL tags however did not use the proper language which resulted in dates and currencies formatted in the locale of the browser. Since we wanted tight control of the language used, this behaviour was not an option for us. We wanted a Portal wide solution which did not impact application design/implementation. 

The portal documentation mentions something about a locale filter. Portal itself provides two implementations of this filter: ExtendedLocaleFilter and SortingLocaleFilter. It is also possible to implement a custom filter.

The code of the provided locale filters can be found in C:\IBM\WP61\PortalServer\base\wp.engine.impl\shared\app\wp.engine.impl.jar file.

Decompiling those filters you can see that the implementation reflects the above language selection process. The filters use the Puma API to determine the user’s locale. Also, by looking at this implementation you get a good idea of how to efficiently build a custom locale filter.

The following filter is an example filter which always fixes the locale to en_US regardless of the user profile or browser setting.



   1:  public class FixedLocaleFilter implements Filter {
   2:      public void init(final FilterConfig aFilterConfig) throws ServletException {
   3:      }
   4:   
   5:      public void doFilter(final ServletRequest aServletRequest, final ServletResponse aResponse, final FilterChain aFilterChain) throws IOException, ServletException {
   6:          if (aServletRequest instanceof HttpServletRequest) {
   7:              aFilterChain.doFilter(new FixedLocaleHttpServletRequest((HttpServletRequest) aServletRequest), aResponse);
   8:          } else {
   9:              aFilterChain.doFilter(aServletRequest, aResponse);
  10:          }
  11:      }
  12:   
  13:      public void destroy() {
  14:   
  15:      }
  16:  }
  17:   
  18:  public class FixedLocaleHttpServletRequest extends HttpServletRequestWrapper {
  19:      private static final Locale EN = new Locale("en", "US");
  20:      private static final Vector<Locale> LOCALES = new Vector<Locale>() {{
  21:          add(EN);
  22:      }};
  23:      private static final Vector<String> ACCEPT_LANGUAGE_EN_US = new Vector<String>() {{
  24:          add("en-US");
  25:      }};
  26:   
  27:      public FixedLocaleHttpServletRequest (HttpServletRequest request) {
  28:          super(request);
  29:      }
  30:   
  31:      @Override
  32:      public Locale getLocale() {
  33:          return EN;
  34:      }
  35:   
  36:      @Override
  37:      public Enumeration getLocales() {
  38:          return LOCALES.elements();
  39:      }
  40:   
  41:      public String getHeader(String header) {
  42:          if (isAcceptLanguageHeader(header))
  43:              return "en-US";
  44:          else
  45:              return super.getHeader(header);
  46:      }
  47:   
  48:      public Enumeration getHeaders(String s) {
  49:          if (isAcceptLanguageHeader(s))
  50:              return ACCEPT_LANGUAGE_EN_US.elements();
  51:          else
  52:              return super.getHeaders(s);
  53:      }
  54:   
  55:      private boolean isAcceptLanguageHeader(String s) {
  56:          return s.intern() == "Accept-Language" || s.equalsIgnoreCase("Accept-Language");
  57:      }
  58:  }

The above FixedLocaleFilter just wraps the incoming request in a FixedLocaleHttpServletRequest. The next step is to configure this filter in the web.xml of the Portal application web.xml. The web.xml can be found in wp_profile_root/config/cells/node-name/applications/wps.ear/deployments/wps/wps.war/WEB-INF.

See the following example configuration:



   1:  <filter>
   2:          <filter-name>Locale Filter</filter-name>
   3:          <!--<filter-class>com.ibm.wps.engine.ExtendedLocaleFilter</filter-class>-->
   4:          <filter-class>FixedLocaleFilter</filter-class>
   5:      </filter>

Please note that the above sample code is just an example unless you want a fixed locale of en_US for the whole profile where the portal is deployed. A practical scenario would be a custom filter which uses the following language selection process:
  1. If the user has set a locale in its profile use that one.
  2. If the user has not set a locale use the browsers locale if the locale is supported.
  3. If the browser locale is not supported use the default locale of the portal.
The above steps can easily be implemented using a custom locale filter. You can also use init parameters to make the filter configurable.

Conclusion
Letting all applications in a WebSphere portal behave the same regarding locale resolution can be hard/impossible if you do not want to implement custom locale handling code within the applications itself. By using a custom locale filter in WebSphere portal, the locale resolution code is kept in one place. This makes locale resolution transparent and clear to all applications running in the portal.