Search code examples
javalocalizationinternationalizationstruts2migration

LocalizedTextUtil Alternative in Struts2 6.1.2.1


I am trying to migrate from Struts2 2.3.36 to Struts2 6.1.2.1 for a legacy multi-module web application.
In every module's init file we register the locale message properties file like so -

//inside init class of XYZ module 
static {
    LocalizedTextUtil.addDefaultResourceBundle("XYZMessageResources");
}

However, after upgrading the struts2 dependency, I found out that this class has been removed since Struts2 2.5.17 and after going through the source code of the Apache Struts2 I found that StrutsLocalizedTextProviderand GlobalLocalizedTextProvider provides similar methods. So I replaced the above line in all init classes like so -

static {
    StrutsLocalizedTextProvider strutsLocalizedTextProvider = new StrutsLocalizedTextProvider();
    strutsLocalizedTextProvider.addDefaultResourceBundle("XYZMessageResources");
}

Adding this fixed the compilation issues.

Also, we have a custom TextProviderSupport class, which implements com.opensymphony.xwork2.TextProvider class, and has some extra methods to convert ANSI to UTF8. This class looks something like this -

public class TextProviderSupport implements TextProvider {
    private static final Log LOGGER = LogFactory.getLog(TextProviderSupport.class);

    final private DefaultTextProvider mProvider =new DefaultTextProvider();
    
     public String getText(final String key, final List args) {
        final String msg = mProvider.getText(key, args);
        return convertFromANSIToUTF8(key, null, msg, args);
     }
//All other overloaded getText methods are overridden similarly which have some business logic 

this TextProviderSupport class is being pushed to the invocation stack inside a custom implementation of the I18nInterceptor class :

public class I18nInterceptor implements Interceptor { 

    //intercept method 
    public String intercept(final ActionInvocation invocation) throws Exception {
        invocation.addPreResultListener(new I18nHandler());
        // get requested locale
        Object requested_locale = invocation.getInvocationContext().getLocale();
        // save it in session
        final Map session = invocation.getInvocationContext().getSession();
        if (session != null) {
            if (requested_locale != null) {
                final Locale locale = (requested_locale instanceof Locale) ? 
                        (Locale)requested_locale
                        :StrutsLocalizedTextProvider.localeFromString(requested_locale.toString(), null);
              if (locale != null) {
                    // put the locale in the session
                    session.put(attributeName, locale);

                    // put the javaScript pattern in the session so that
                    // we can access it in Header.jsp
                    final String aPattern = getAndUpdateDatePattern(locale);
                    session.put(patternDate, aPattern);
                }
            }
            // this piece of code checks that a pattern for date has been set
            // if not, it sets one according to context locale
            if (!session.containsKey(patternDate)) {
                final String aPattern = getAndUpdateDatePattern(invocation
                        .getInvocationContext().getLocale());
                session.put(patternDate, aPattern);
            }

            // set locale for action
            final Object locale = session.get(attributeName);
            if (locale != null && locale instanceof Locale) {
                saveLocale(invocation, (Locale) locale);
            }
        }

        return invocation.invoke();
    }
protected void saveLocale(final ActionInvocation invocation, final Locale locale) {
        //ActionSupport actionInstance = (ActionSupport) invocation.getAction();
        //ActionContext.getContext().setLocale(locale);
        invocation.getInvocationContext().withLocale(locale);
    }

private static class I18nHandler implements PreResultListener {
        public void beforeResult(final ActionInvocation invocation,
                                 final String resultCode) {
            // Push com text provider on top of the stack
            invocation.getStack().push(new TextProviderSupport());
        }
    }

and this TextProviderSupport class is added in our own ActionSupport class which extends com.opensymphony.xwork2.ActionSupport class-

public class ActionSupport extends com.opensymphony.xwork2.ActionSupport {
private final transient TextProvider textProvider =  new TextProviderSupport();

    public TextProvider getTextProviderInstance() {
      return textProvider;
    }

However, upon debugging, I found that the protected LocalizedTextProvider localizedTextProvider field is always null and when I try to deploy the application war, i get NPE in line 72 of the getText method in com.opensymphony.xwork2.DefaultTextProvider class-

public String getText(String key, List<?> args) {
        Object[] params;
        if (args != null) {
            params = args.toArray();
        } else {
            params = EMPTY_ARGS;
        }
       //NPE on this line as localizedTextProvider is null
        return localizedTextProvider.findDefaultText(key, ActionContext.getContext().getLocale(), params);
    }

the Stacktrace -

Caused by: java.lang.NullPointerException
        at com.opensymphony.xwork2.DefaultTextProvider.getText(DefaultTextProvider.java:72)
        at com.opensymphony.xwork2.DefaultTextProvider.getText(DefaultTextProvider.java:87)
        at com.opensymphony.xwork2.DefaultTextProvider.getText(DefaultTextProvider.java:134)
        at XXXXX.XXXX.XXXX.i18n.TextProviderSupport.getText(TextProviderSupport.java:73)
        at org.apache.struts2.util.TextProviderHelper.getText(TextProviderHelper.java:55)
        at org.apache.struts2.components.Text.end(Text.java:183)
        at org.apache.struts2.views.jsp.ComponentTagSupport.doEndTag(ComponentTagSupport.java:40)
        at org.apache.jsp.hac.redirectlogin_jsp._jspx_meth_s_005ftext_005f0(redirectlogin_jsp.java:228)
        at org.apache.jsp.hac.redirectlogin_jsp._jspService(redirectlogin_jsp.java:112)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
        at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:433)
        ... 44 more

I came across a similar problem on Stackoverflow - Migrate from Struts 2.3.33 to Struts 2.5.12 LocalizedTextUtil is removed

and it suggested implementing a CustomStrutsTextProviderFactory class, but I am not able to understand how to use this class in my application and where I need to wire it.


Solution

  • I used this class ( look at inject it is from com.opensymphony.xwork2.inject):

    @WebListener
    public class CustomStrutsTextProviderFactory extends 
            StrutsTextProviderFactory  implements ServletContextListener{
    
        protected LocalizedTextProvider localizedTextProvider;
    
        @Override
        @Inject //this should be from com.opensymphony.xwork2.inject.Inject;
        public void setLocalizedTextProvider(LocalizedTextProvider localizedTextProvider) {          
           localizedTextProvider.addDefaultResourceBundle("messages/resources");
           localizedTextProvider.addDefaultResourceBundle("messages/help");
        }
    
        @Override
        protected TextProvider getTextProvider( Class clazz) {      
            return new CustomTextProvider(clazz, localeProviderFactory.createLocaleProvider(), localizedTextProvider);
        }
    
      @Override
      protected TextProvider getTextProvider(ResourceBundle bundle) {
            return new CustomTextProvider(bundle, localeProviderFactory.createLocaleProvider(), localizedTextProvider);
        }
        
    }
    

    and in struts.xml

    <bean type="com.opensymphony.xwork2.TextProviderFactory" name="customStrutsTextProviderFactory" class="foo.bar.CustomStrutsTextProviderFactory" scope="singleton"/>