Uploaded image for project: 'Struts 2'
  1. Struts 2
  2. WW-4813

NP with TextProvider and wildcardmapping

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Resolved
    • Affects Version/s: 2.5.12
    • Fix Version/s: 2.5.13
    • Component/s: Core
    • Labels:
      None
    • Environment:

      Linux / FF / Java 8

      Description

      Probably another corner case around TextProvider in connection with wildcard mapping:

      Embeded elements (in URLs, image tags...) in jsp like <s:text name="edit"/> throw an NP-Exception.

      struts.xml

              <action name="**" class="org.package.MyClassDoingNothing">
                <result name="test">/view/test.jsp</result>
              </action>
      

      test.jsp contains an element like this:

      <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/>
      

      This will give since 2.5.12 null pointer exception:

      ERROR DefaultDispatcherErrorHandler - Exception occurred during processing request: java.lang.NullPointerException
      org.apache.jasper.JasperException: java.lang.NullPointerException
      	at org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:591)
      	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:476)
      	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396)
      	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340)
      	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:719)
      	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:465)
      	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:390)
      	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:317)
      	at org.apache.struts2.result.ServletDispatcherResult.doExecute(ServletDispatcherResult.java:173)
      	at org.apache.struts2.result.StrutsResultSupport.execute(StrutsResultSupport.java:208)
      	at com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:373)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:277)
      	at org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:253)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:176)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:260)
      	at org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:52)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.doIntercept(ConversionErrorInterceptor.java:139)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:134)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:134)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:199)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:69)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.DateTextFieldInterceptor.intercept(DateTextFieldInterceptor.java:115)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:88)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:246)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:99)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:139)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:157)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:174)
      	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:123)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:171)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:201)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:193)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at util.AuthenticationInterceptor.intercept(AuthenticationInterceptor.java:88)
      	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
      	at org.apache.struts2.factory.StrutsActionProxy.execute(StrutsActionProxy.java:53)
      	at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:577)
      	at org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:81)
      	at org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:143)
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      	at customization.RequestFilter.doFilter(RequestFilter.java:57)
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
      	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
      	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
      	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
      	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
      	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
      	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
      	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521)
      	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096)
      	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674)
      	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
      	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      	at java.lang.Thread.run(Thread.java:748)
      Caused by: java.lang.NullPointerException
      	at com.opensymphony.xwork2.ActionSupport.getTextProvider(ActionSupport.java:278)
      	at com.opensymphony.xwork2.ActionSupport.getText(ActionSupport.java:111)
      	at org.apache.struts2.util.TextProviderHelper.getText(TextProviderHelper.java:76)
      	at org.apache.struts2.components.Text.end(Text.java:162)
      	at org.apache.struts2.views.jsp.ComponentTagSupport.doEndTag(ComponentTagSupport.java:42)
      	at org.apache.jsp.view.admin_jsp._jspx_meth_s_005ftext_005f23(admin_jsp.java:1441)
      	at org.apache.jsp.view.admin_jsp._jspx_meth_s_005fiterator_005f0(admin_jsp.java:1331)
      	at org.apache.jsp.view.admin_jsp._jspx_meth_s_005fif_005f0(admin_jsp.java:1242)
      	at org.apache.jsp.view.admin_jsp._jspService(admin_jsp.java:269)
      	at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
      	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
      	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438)
      	... 88 more
      

        Activity

        Hide
        lukaszlenart Lukasz Lenart added a comment -

        Cool Not everybody knows that Struts contains its own DI mechanism that can be used as above

        Show
        lukaszlenart Lukasz Lenart added a comment - Cool Not everybody knows that Struts contains its own DI mechanism that can be used as above
        Hide
        flyingfischer Markus Fischer added a comment -

        This may be closed. Thanks for all your valuable input, time and good work!

        I think this here serves as a documentation for future reference. <bean type="util.Texti18n" class="util.Texti18n" /> finally did it for me.

        Show
        flyingfischer Markus Fischer added a comment - This may be closed. Thanks for all your valuable input, time and good work! I think this here serves as a documentation for future reference. <bean type="util.Texti18n" class="util.Texti18n" /> finally did it for me.
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        I assume that this can be marked as resolved, right? Or do you expect some changes in Struts?

        Show
        lukaszlenart Lukasz Lenart added a comment - I assume that this can be marked as resolved, right? Or do you expect some changes in Struts?
        Hide
        lukaszlenart Lukasz Lenart added a comment - - edited

        It isn't a static util anymore, so in this meaning yes, you cannot use it as previously. But the StrutsLocalizedTextProvider and GlobalLocalizedTextProvider implementations of the LocalizedTextProvider don't base on the ActionContext so you can use them out of the ActionContext as a normal class:

        LocalizedTextProvider provider = new GlobalLocalizedTextProvider();
        provider.findDefaultText("myKey", Locale.US);
        

        You can wrap it with a static util class but this will be your solution and gives more freedom to me as a maintainer of the project to modify the LocalizedTextProvider when required, e.g. I can mark the existing implementations as @Deprecated and implement new versions.

        Show
        lukaszlenart Lukasz Lenart added a comment - - edited It isn't a static util anymore, so in this meaning yes, you cannot use it as previously. But the StrutsLocalizedTextProvider and GlobalLocalizedTextProvider implementations of the LocalizedTextProvider don't base on the ActionContext so you can use them out of the ActionContext as a normal class: LocalizedTextProvider provider = new GlobalLocalizedTextProvider(); provider.findDefaultText( "myKey" , Locale.US); You can wrap it with a static util class but this will be your solution and gives more freedom to me as a maintainer of the project to modify the LocalizedTextProvider when required, e.g. I can mark the existing implementations as @Deprecated and implement new versions.
        Hide
        aleksandr-m Aleksandr Mashchenko added a comment -

        Lukasz Lenart So there is no way to access LocalizedTextProvider in another thread / outside of action context?

        Show
        aleksandr-m Aleksandr Mashchenko added a comment - Lukasz Lenart So there is no way to access LocalizedTextProvider in another thread / outside of action context?
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        Right, the 2 requires a bean definition in a struts.xml i.e.:

        <bean type="util.Texti18n"/>
        
        Show
        lukaszlenart Lukasz Lenart added a comment - Right, the 2 requires a bean definition in a struts.xml i.e.: <bean type= "util.Texti18n" />
        Hide
        flyingfischer Markus Fischer added a comment -

        Thanks for this great documentation!

        Version 1 and 3 do work perfect. However version 2 does lead to an Exception, at least if called in a Class extending ActionSupport:

        com.opensymphony.xwork2.inject.DependencyException: com.opensymphony.xwork2.inject.ContainerImpl$MissingDependencyException: No mapping found for dependency [type=util.Texti18n, name='default'] in public void ch.myPackage.myClass.setI18n(util.Texti18n).
        	com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMembers(ContainerImpl.java:145)
        	com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMethods(ContainerImpl.java:116)
        	com.opensymphony.xwork2.inject.ContainerImpl.addInjectors(ContainerImpl.java:94)
        	com.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:75)
        	com.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:71)
        	com.opensymphony.xwork2.inject.util.ReferenceCache$CallableCreate.call(ReferenceCache.java:155)
        	java.util.concurrent.FutureTask.run(FutureTask.java:266)
        	com.opensymphony.xwork2.inject.util.ReferenceCache.internalCreate(ReferenceCache.java:79)
        	com.opensymphony.xwork2.inject.util.ReferenceCache.get(ReferenceCache.java:123)
        	com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:468)
        	com.opensymphony.xwork2.inject.ContainerImpl$6.call(ContainerImpl.java:507)
        	com.opensymphony.xwork2.inject.ContainerImpl$6.call(ContainerImpl.java:505)
        	com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:560)
        	com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:505)
        	com.opensymphony.xwork2.ObjectFactory.injectInternalBeans(ObjectFactory.java:158)
        	com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:188)
        	com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:172)
        	com.opensymphony.xwork2.factory.DefaultActionFactory.buildAction(DefaultActionFactory.java:22)
        	com.opensymphony.xwork2.ObjectFactory.buildAction(ObjectFactory.java:137)
        	com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:299)
        	com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:399)
        	com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:204)
        	org.apache.struts2.factory.StrutsActionProxy.prepare(StrutsActionProxy.java:62)
        	org.apache.struts2.factory.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:37)
        	com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:58)
        	org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:567)
        	org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:81)
        	org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:143)
        

        Maybe I am doing this wrong, but using i18n seems to remain quite a bit mor complex than it used to be. Do not worry too much. My helper class Texti18n does work perfectly and your inputs above do open some more usable options.

        Show
        flyingfischer Markus Fischer added a comment - Thanks for this great documentation! Version 1 and 3 do work perfect. However version 2 does lead to an Exception, at least if called in a Class extending ActionSupport : com.opensymphony.xwork2.inject.DependencyException: com.opensymphony.xwork2.inject.ContainerImpl$MissingDependencyException: No mapping found for dependency [type=util.Texti18n, name=' default '] in public void ch.myPackage.myClass.setI18n(util.Texti18n). com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMembers(ContainerImpl.java:145) com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMethods(ContainerImpl.java:116) com.opensymphony.xwork2.inject.ContainerImpl.addInjectors(ContainerImpl.java:94) com.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:75) com.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:71) com.opensymphony.xwork2.inject.util.ReferenceCache$CallableCreate.call(ReferenceCache.java:155) java.util.concurrent.FutureTask.run(FutureTask.java:266) com.opensymphony.xwork2.inject.util.ReferenceCache.internalCreate(ReferenceCache.java:79) com.opensymphony.xwork2.inject.util.ReferenceCache.get(ReferenceCache.java:123) com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:468) com.opensymphony.xwork2.inject.ContainerImpl$6.call(ContainerImpl.java:507) com.opensymphony.xwork2.inject.ContainerImpl$6.call(ContainerImpl.java:505) com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:560) com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:505) com.opensymphony.xwork2.ObjectFactory.injectInternalBeans(ObjectFactory.java:158) com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:188) com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:172) com.opensymphony.xwork2.factory.DefaultActionFactory.buildAction(DefaultActionFactory.java:22) com.opensymphony.xwork2.ObjectFactory.buildAction(ObjectFactory.java:137) com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:299) com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:399) com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:204) org.apache.struts2.factory.StrutsActionProxy.prepare(StrutsActionProxy.java:62) org.apache.struts2.factory.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:37) com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:58) org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:567) org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:81) org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:143) Maybe I am doing this wrong, but using i18n seems to remain quite a bit mor complex than it used to be. Do not worry too much. My helper class Texti18n does work perfectly and your inputs above do open some more usable options.
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        ActionSupport should only be used for actions, using this class as a base class for utilities is a bad idea (just pointing this out for others reading this issue).

        You current approach is fine and you can always use the container to inject the dependencies - but this can only be used in context of an action or with support of ActionContext, see the examples below:

        implementation using @Inject
        public class Texti18n {
        
            private final LocalizedTextProvider localizedTextProvider;
            private final LocaleProviderFactory localeProviderFactory;
        
            @Inject
            public Texti18n(LocalizedTextProvider localizedTextProvider, LocaleProviderFactory localeProviderFactory) {
                this.localizedTextProvider = localizedTextProvider;
                this.localeProviderFactory = localeProviderFactory;
            }
        
            public String getText(final String key) {
                final String value = localizedTextProvider.findDefaultText(key, getLocale());
                if (value != null) {
                    return value;
                }
                return key;
            }
        
            public Locale getLocale() {
                return localeProviderFactory.createLocaleProvider().getLocale();
            }
        }
        

        1.

        usage inside an action
        public String execute() {
            String message = container.inject(Texti18n.class).getText("my.key");
            ....
        }
        

        or

        2.

        define a field with a setter annotated with @Inject
        public class MyAction { // you don't have to extend ActionSupport
             private Texti18n texti18n;
        
            @Inject
            public void setTexti18n(Texti18n texti18n) {
                this.texti18n = texti18n
            }
        
            public String execute() {
                 String message = texti18n.getText("my.key");
            }
        }
        

        3.

        out of the action context (but still it must be during action request lifecycle)
        public MyUtil {
        
            public static String getText(String key) {
                return ActionContext.getContext().getContainer().inject(Texti18n.class).getText(key);
            }
        
        }
        
        Show
        lukaszlenart Lukasz Lenart added a comment - ActionSupport should only be used for actions, using this class as a base class for utilities is a bad idea (just pointing this out for others reading this issue). You current approach is fine and you can always use the container to inject the dependencies - but this can only be used in context of an action or with support of ActionContext , see the examples below: implementation using @Inject public class Texti18n { private final LocalizedTextProvider localizedTextProvider; private final LocaleProviderFactory localeProviderFactory; @Inject public Texti18n(LocalizedTextProvider localizedTextProvider, LocaleProviderFactory localeProviderFactory) { this .localizedTextProvider = localizedTextProvider; this .localeProviderFactory = localeProviderFactory; } public String getText( final String key) { final String value = localizedTextProvider.findDefaultText(key, getLocale()); if (value != null ) { return value; } return key; } public Locale getLocale() { return localeProviderFactory.createLocaleProvider().getLocale(); } } 1. usage inside an action public String execute() { String message = container.inject(Texti18n.class).getText( "my.key" ); .... } or 2. define a field with a setter annotated with @Inject public class MyAction { // you don't have to extend ActionSupport private Texti18n texti18n; @Inject public void setTexti18n(Texti18n texti18n) { this .texti18n = texti18n } public String execute() { String message = texti18n.getText( "my.key" ); } } 3. out of the action context (but still it must be during action request lifecycle) public MyUtil { public static String getText( String key) { return ActionContext.getContext().getContainer().inject(Texti18n.class).getText(key); } }
        Hide
        flyingfischer Markus Fischer added a comment - - edited

        I use ActionSupport for the appropriate Action classes and used to use ActionSupport also for utility classes which needed getText.

        I now use Texti18n i18n = new Texti18n(); and then i18n.getText(key); as a substitute. Do you see a better approach?

        Show
        flyingfischer Markus Fischer added a comment - - edited I use ActionSupport for the appropriate Action classes and used to use ActionSupport also for utility classes which needed getText . I now use Texti18n i18n = new Texti18n(); and then i18n.getText(key); as a substitute. Do you see a better approach?
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        Do you use ActionSupport because of the getText functionality? If so I wonder if this is a good approach as ActionSupport provides much more functions and responsibilities.

        How do you create the Texti18n?

        Show
        lukaszlenart Lukasz Lenart added a comment - Do you use ActionSupport because of the getText functionality? If so I wonder if this is a good approach as ActionSupport provides much more functions and responsibilities. How do you create the Texti18n ?
        Hide
        flyingfischer Markus Fischer added a comment -

        Yes, I did create the utility classes by hand and put them inside the Lists.

        My helper class to get around and resolve the issues around TextProvider looks like this. Would be nice to see an (improved) official method from Struts

        package util;
        
        import java.util.Locale;
        
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        
        import com.opensymphony.xwork2.ActionContext;
        import com.opensymphony.xwork2.LocaleProviderFactory;
        import com.opensymphony.xwork2.LocalizedTextProvider;
        
        public class Texti18n {
        
            private final Logger LOG = LoggerFactory.getLogger(Texti18n.class);
        
            /**
             * Returns i18n text messages from the default bundles for utility classes which cannot
             * extend ActionSupport to use i18n support, due to a change in behavior with struts > 2.5.2.
             * Returns the value for a key depending on the locale present. For missing i18n keys in a given
             * language bundle, the default bundle (English) will be used. If a key is not present at all, the key will
             * be returned instead of a value.
             */
            public String getText(final String key) {
        
                final LocalizedTextProvider provider = ActionContext.getContext().getInstance(LocalizedTextProvider.class);
                final String value = provider.findDefaultText(key, getLocale());
        
                if (value != null) {
                    return value;
                }
        
                return key;
            }
        
            /**
             * Returns the Locale present in the ActionContext. The Locale.US will be returned as default,
             * if the Locale is invalid.
             */
            public Locale getLocale() {
        
                Locale locale = null;
        
                try {
        
                    final LocaleProviderFactory factory = ActionContext.getContext().getInstance(LocaleProviderFactory.class);
                    locale = factory.createLocaleProvider().getLocale();
                    // validate locale
                    locale.getISO3Language(); // may throw an MissingResourceException
                    return locale;
                } catch (final Exception ex) {
                    LOG.error("Not a valid locale: " + locale);
                }
                // default language
                return Locale.US;
            }
        }
        
        Show
        flyingfischer Markus Fischer added a comment - Yes, I did create the utility classes by hand and put them inside the Lists. My helper class to get around and resolve the issues around TextProvider looks like this. Would be nice to see an (improved) official method from Struts package util; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.LocaleProviderFactory; import com.opensymphony.xwork2.LocalizedTextProvider; public class Texti18n { private final Logger LOG = LoggerFactory.getLogger(Texti18n.class); /** * Returns i18n text messages from the default bundles for utility classes which cannot * extend ActionSupport to use i18n support, due to a change in behavior with struts > 2.5.2. * Returns the value for a key depending on the locale present. For missing i18n keys in a given * language bundle, the default bundle (English) will be used. If a key is not present at all, the key will * be returned instead of a value. */ public String getText( final String key) { final LocalizedTextProvider provider = ActionContext.getContext().getInstance(LocalizedTextProvider.class); final String value = provider.findDefaultText(key, getLocale()); if (value != null ) { return value; } return key; } /** * Returns the Locale present in the ActionContext. The Locale.US will be returned as default , * if the Locale is invalid. */ public Locale getLocale() { Locale locale = null ; try { final LocaleProviderFactory factory = ActionContext.getContext().getInstance(LocaleProviderFactory.class); locale = factory.createLocaleProvider().getLocale(); // validate locale locale.getISO3Language(); // may throw an MissingResourceException return locale; } catch ( final Exception ex) { LOG.error( "Not a valid locale: " + locale); } // default language return Locale.US; } }
        Hide
        lukaszlenart Lukasz Lenart added a comment - - edited

        does fill in Lists of other UtilityClasses partially also extending ActionSupport

        You create those utilities by hand and put them inside the Lists, right?

        Show
        lukaszlenart Lukasz Lenart added a comment - - edited does fill in Lists of other UtilityClasses partially also extending ActionSupport You create those utilities by hand and put them inside the Lists, right?
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        Hm... so the main case here is that you want to use a context-less version of getText(). This means that you can only base on global properties files, no fancy hierarchy scans (finding package.properties from bottom to top) or class name related bundles (e.g. MyClassDoingNothing.properties).

        If so this should be doable

        Show
        lukaszlenart Lukasz Lenart added a comment - Hm... so the main case here is that you want to use a context-less version of getText() . This means that you can only base on global properties files, no fancy hierarchy scans (finding package.properties from bottom to top) or class name related bundles (e.g. MyClassDoingNothing.properties ). If so this should be doable
        Hide
        flyingfischer Markus Fischer added a comment -

        I found the root cause. Nothing to do with wildcard mapping. The issue is bound to the pre 2.5.2 way getText(key) for any class extending ActionSupport. In this project, in contrast to other more complex ones, this worked out until 2.5.10.1:

        The main action class MyClassDoingNothing extending ActionSupport does several things. It holds a reference to a Utility class also extending ActionSupport, which does fill in Lists of other UtilityClasses partially also extending ActionSupport. I did this to be able to call getText(key) in some validation context. Now this seems to set container to null after 2.5.12 leading to a NP.

        As soon as I replace the Struts method getText(key) with my custom wrapper Texti18n.getText(key) and remove the extends ActionSupport in the Utility classes, the NP is gone.

        Now obviously I did use ActionSupport not in the correct context. I'd still vote for a handy way to use a something like getText(key) as Struts method thorugh any point in a project.

        Show
        flyingfischer Markus Fischer added a comment - I found the root cause. Nothing to do with wildcard mapping. The issue is bound to the pre 2.5.2 way getText(key) for any class extending ActionSupport . In this project, in contrast to other more complex ones, this worked out until 2.5.10.1: The main action class MyClassDoingNothing extending ActionSupport does several things. It holds a reference to a Utility class also extending ActionSupport , which does fill in Lists of other UtilityClasses partially also extending ActionSupport . I did this to be able to call getText(key) in some validation context. Now this seems to set container to null after 2.5.12 leading to a NP. As soon as I replace the Struts method getText(key) with my custom wrapper Texti18n.getText(key) and remove the extends ActionSupport in the Utility classes, the NP is gone. Now obviously I did use ActionSupport not in the correct context. I'd still vote for a handy way to use a something like getText(key) as Struts method thorugh any point in a project.
        Hide
        flyingfischer Markus Fischer added a comment -

        If I leave <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> and use <s:text name="edit"/>, it works. Also, of course, if I use hardcoded values <img title="edit" alt="edit" src="editImage.jpg"/>.

        Just reverted to 2.5.10.1 to recheck: no issue. Going back to 2.5.12 brings back the issue.

        How can it be, that container in ActionSupport on line 278 is null? I suspect some tricky complications with wildcard mapping.

        Show
        flyingfischer Markus Fischer added a comment - If I leave <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> and use <s:text name="edit"/> , it works. Also, of course, if I use hardcoded values <img title="edit" alt="edit" src="editImage.jpg"/> . Just reverted to 2.5.10.1 to recheck: no issue. Going back to 2.5.12 brings back the issue. How can it be, that container in ActionSupport on line 278 is null? I suspect some tricky complications with wildcard mapping.
        Hide
        lukaszlenart Lukasz Lenart added a comment - - edited

        Can you leave <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> but add something else, e.g. <s:text name="edit"/>?

        Show
        lukaszlenart Lukasz Lenart added a comment - - edited Can you leave <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> but add something else, e.g. <s:text name="edit"/> ?
        Hide
        flyingfischer Markus Fischer added a comment -

        The strange thing is also, if I remove everything from test.jsp and use only <s:text name="edit"/>, it works. As soon as I add <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> I get a NP.

        Show
        flyingfischer Markus Fischer added a comment - The strange thing is also, if I remove everything from test.jsp and use only <s:text name="edit"/> , it works. As soon as I add <img title="<s:text name="edit"/>" alt="<s:text name="edit"/>" src="editImage.jpg"/> I get a NP.
        Hide
        flyingfischer Markus Fischer added a comment - - edited

        The action is called by a POST method:

         <form action="test" method="POST" accept-charset="UTF-8">
         ....
        </form>
        

        Just retested: NP is also happening with GET

        Show
        flyingfischer Markus Fischer added a comment - - edited The action is called by a POST method: <form action= "test" method= "POST" accept-charset= "UTF-8" > .... </form> Just retested: NP is also happening with GET
        Hide
        flyingfischer Markus Fischer added a comment -

        MyClassDoingNothing extends ActionSupport and implements ServletRequestAware, ServletResponseAware.

        Show
        flyingfischer Markus Fischer added a comment - MyClassDoingNothing extends ActionSupport and implements ServletRequestAware, ServletResponseAware .
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        This works like a charm

                <action name="**">
                    <result>/WEB-INF/test.jsp</result>
                </action>
        

        and accessing via http://localhost:8080/aas.action

        Show
        lukaszlenart Lukasz Lenart added a comment - This works like a charm <action name= "**" > <result> /WEB-INF/test.jsp </result> </action> and accessing via http://localhost:8080/aas.action
        Hide
        lukaszlenart Lukasz Lenart added a comment - - edited

        Does your MyClassDoingNothing extend the ActionSupport class?

        Show
        lukaszlenart Lukasz Lenart added a comment - - edited Does your MyClassDoingNothing extend the ActionSupport class?
        Hide
        lukaszlenart Lukasz Lenart added a comment -

        Strange ... action wasn't properly instantiated as container is null :\ How do you access this action? What's the url?

        https://github.com/apache/struts/blob/master/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java#L278

        Show
        lukaszlenart Lukasz Lenart added a comment - Strange ... action wasn't properly instantiated as container is null :\ How do you access this action? What's the url? https://github.com/apache/struts/blob/master/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java#L278

          People

          • Assignee:
            Unassigned
            Reporter:
            flyingfischer Markus Fischer
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development