Uploaded image for project: 'Confluence Data Center'
  1. Confluence Data Center
  2. CONFSERVER-79630

Create compat-lib to handle basic compat API for Webwork/Struts

      Atlassian Update - Oct 2022

      The Confluence DC development team has added a section for confluence-compat-lib usage with Struts2. The layer for Struts2 compatibility has been added in 1.5.0 of confluence-compat-lib.

      We would like to say thank you for everyone who provided feedback for the compatibility layer and would continue to watch the ticket/community post for the same.

      Regards,
      The Confluence DC Team

      Issue Summary

      Since Struts 2 upgrade is a breaking change, plugins would require a compatibility layer to keep a single version running across 7.x/8.x of Confluence.

      We can provide it to cover >90% of the cases. This would help in reducing friction in Vendor adoption of 8.0

      Few compatibility layers required inside confluence-compat-lib would need to cater for:

      • Constants
      • ActionContext/ServletActionContext
      • VelocityUtils bridge for static methods(if needed)

      This is reproducible on Data Center: (yes) 

      Steps to Reproduce

      1. Use Struts based Confluence 8.0

      Expected Results

      Single version of plugin using ServletActionContext can work with both Confluence 7.x and 8.x

      Actual Results

      Currently, two different versions are required.

      Workaround

      Currently there is no known workaround for this behavior. Plugins would need two versions for compatibility.

          Form Name

            [CONFSERVER-79630] Create compat-lib to handle basic compat API for Webwork/Struts

            f68b7568bf9b:

            Instead of waiting for the Confluence team to implement specific features, we've found it useful to implement our own code that gets dynamically loaded based on the Confluence version being used...then you can implement whatever you need.

            You need to declare all of the relevant version-specific packages you are using as 'resolution:="optional"' imports in the pom's <Import-Package>, and then create a FactoryBean like the following that will load the implementation appropriate to the Confluence version in use.

            For example, as written in the following snippet, you can freely inject a "FrameworkManager" into any other part of your code. The underlying bean will be instantiated as either a StrutsFrameworkManager or an XworkFrameworkManager class, from which you can then import classes that are only available in one Confluence version or another (such as creating Xwork/Struts interceptors).

            This example is written using the Spring scanner, but it can presumably be adjusted for Java spring config.

            To make this work, "FrameworkManager" needs to be created as an interface, and then StrutsFrameworkManager/XworkFrameworkManager need to be created as classes that implement that interface. Do not tag these two remaining classes with the @Component annotation, because you want them optionally loaded by the FactoryBean below, rather than being instantiated on startup. (You can tag their constructors with @Inject though.)

            // For VersionKit sources, see: https://developer.atlassian.com/server/jira/platform/jira-agile-adg-migration-guidelines/
            
            import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
            import com.atlassian.sal.api.ApplicationProperties;
            import org.springframework.beans.factory.FactoryBean;
            import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
            import org.springframework.stereotype.Component;
            
            import javax.inject.Inject;
            
            @Component(value="frameworkManager")
            public class FrameworkManagerFactoryBean implements FactoryBean<FrameworkManager>
            {
                private static final String STRUTS_CONFLUENCE_VERSION = "8.0";
            
                @Inject @ComponentImport private ApplicationProperties applicationProperties;
                @Inject private AutowireCapableBeanFactory beanFactory;
            
                @Override
                public FrameworkManager getObject()
                {
                    Class<? extends FrameworkManager> requiredClass =
                            (VersionKit.isGreaterOrEqualTo(applicationProperties.getVersion(), STRUTS_CONFLUENCE_VERSION))
                                ? StrutsFrameworkManager.class
                                : XworkFrameworkManager.class;
            
                    return beanFactory.createBean(requiredClass);
                }
            
                @Override
                public Class<? extends FrameworkManager> getObjectType()
                {
                    return FrameworkManager.class;
                }
            
                @Override
                public boolean isSingleton()
                {
                    return true;
                }
            }
            

            Scott Dudley [Cenote] added a comment - f68b7568bf9b : Instead of waiting for the Confluence team to implement specific features, we've found it useful to implement our own code that gets dynamically loaded based on the Confluence version being used...then you can implement whatever you need. You need to declare all of the relevant version-specific packages you are using as 'resolution:="optional"' imports in the pom's <Import-Package>, and then create a FactoryBean like the following that will load the implementation appropriate to the Confluence version in use. For example, as written in the following snippet, you can freely inject a "FrameworkManager" into any other part of your code. The underlying bean will be instantiated as either a StrutsFrameworkManager or an XworkFrameworkManager class, from which you can then import classes that are only available in one Confluence version or another (such as creating Xwork/Struts interceptors). This example is written using the Spring scanner, but it can presumably be adjusted for Java spring config. To make this work, "FrameworkManager" needs to be created as an interface, and then StrutsFrameworkManager/XworkFrameworkManager need to be created as classes that implement that interface. Do not tag these two remaining classes with the @Component annotation, because you want them optionally loaded by the FactoryBean below, rather than being instantiated on startup. (You can tag their constructors with @Inject though.) // For VersionKit sources, see: https://developer.atlassian.com/server/jira/platform/jira-agile-adg-migration-guidelines/ import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.sal.api.ApplicationProperties; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.stereotype.Component; import javax.inject.Inject; @Component(value= "frameworkManager" ) public class FrameworkManagerFactoryBean implements FactoryBean<FrameworkManager> { private static final String STRUTS_CONFLUENCE_VERSION = "8.0" ; @Inject @ComponentImport private ApplicationProperties applicationProperties; @Inject private AutowireCapableBeanFactory beanFactory; @Override public FrameworkManager getObject() { Class <? extends FrameworkManager> requiredClass = (VersionKit.isGreaterOrEqualTo(applicationProperties.getVersion(), STRUTS_CONFLUENCE_VERSION)) ? StrutsFrameworkManager.class : XworkFrameworkManager.class; return beanFactory.createBean(requiredClass); } @Override public Class <? extends FrameworkManager> getObjectType() { return FrameworkManager.class; } @Override public boolean isSingleton() { return true ; } }

            Matthias Clasen added a comment - - edited

            Hi there. We are happy to see and use the new version of the compat-lib. However, in our apps, we also introduce our own interceptors and currently implement com.opensymphony.xwork.interceptor.Interceptor to get that functionality. With Confluence 8 and even with the compat-lib in 1.5.3, it looks like there is no support for com.opensymphony.xwork.interceptor.Interceptor. Would it be possible to add this interface with transition to com.opensymphony.xwork2.interceptor.Interceptor as well? That applies to com.opensymphony.xwork.ActionInvocation too.

            as well?

            Matthias Clasen added a comment - - edited Hi there. We are happy to see and use the new version of the compat-lib. However, in our apps, we also introduce our own interceptors and currently implement com.opensymphony.xwork.interceptor.Interceptor to get that functionality. With Confluence 8 and even with the compat-lib in 1.5.3, it looks like there is no support for com.opensymphony.xwork.interceptor.Interceptor . Would it be possible to add this interface with transition to com.opensymphony.xwork2.interceptor.Interceptor as well? That applies to com.opensymphony.xwork.ActionInvocation too. as well?

            Hi deb4b4fcfa81 We have released a new version (1.5.1) of compat-lib with the fix. We changed getParameters method to return Map instead of HttpParameters to provide backward compatibility. Please verify from you end.

            Bhanu Darisi (Inactive) added a comment - Hi deb4b4fcfa81 We have released a new version (1.5.1) of compat-lib with the fix. We changed getParameters method to return Map instead of HttpParameters to provide backward compatibility. Please verify from you end.

            Hi deb4b4fcfa81  Thanks for the feedback
            Created the below Jira ticket for this issue for tracking.

            https://jira.atlassian.com/browse/CONFSERVER-80174

             

            Bhanu Darisi (Inactive) added a comment - Hi deb4b4fcfa81   Thanks for the feedback Created the below Jira ticket for this issue for tracking. https://jira.atlassian.com/browse/CONFSERVER-80174  

            @Bhanu Darisi: 

            I think I found a bug in the constructor for com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat  

            I reported it in ECOHELP-2696 but nobody has responded to that help ticket.  Can you see that ticket?  Can you or someone else look into it?  I am unable to use ActionContextCompatManager because of this. 

            It works fine in Confluence 7.x but explodes in Confluence 8.x.

            Here are a few details in case you can't get to that ticket:

            ----------------------------

             

            The constructor code is trying to get the ActionContext#setParameters(Map) method but that does not exist in xwork2.  In xwork2 it is ActionContext.setParameters(HttpParameters).

            the ActionContext.setParameters(Map) is only in old xwork ActionContext, as far as I know. See around line 47 in the ActionContextStruts2AndWWCompat constructor:
            this.setParameters = this.getACStruts2Method("setParameters", classLoader, Map.class);
             
             My plugin code that uses ActionContextCompatManager does not blow up in Confluence 7.x... but in Confluence 8.x it blows up w/ this NoSuchMethodException:
            Caused by: java.lang.NoSuchMethodException: com.opensymphony.xwork2.ActionContext.setParameters(java.util.Map)
                at java.base/java.lang.Class.getMethod(Class.java:2108)
                at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.getACStruts2Method(ActionContextStruts2AndWWCompat.java:200)
                at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.<init>(ActionContextStruts2AndWWCompat.java:44)
                at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:34)
                ... 432 more
            com.atlassian.confluence.api.service.exceptions.ServiceException: ActionContext couldn't be initialized.{{}}

            Ture Hoefner {Appfire} added a comment - @Bhanu Darisi:  I think I found a bug in the constructor for com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat   I reported it in ECOHELP-2696 but nobody has responded to that help ticket.  Can you see that ticket?  Can you or someone else look into it?  I am unable to use ActionContextCompatManager because of this.  It works fine in Confluence 7.x but explodes in Confluence 8.x. Here are a few details in case you can't get to that ticket: ----------------------------   The constructor code is trying to get the ActionContext#setParameters( Map ) method but that does not exist in xwork2.  In xwork2 it is ActionContext.setParameters( HttpParameters ). the ActionContext.setParameters( Map ) is only in old xwork ActionContext, as far as I know. See around line 47 in the ActionContextStruts2AndWWCompat constructor: this.setParameters = this.getACStruts2Method("setParameters", classLoader, Map.class);    My plugin code that uses ActionContextCompatManager does not blow up in Confluence 7.x... but in Confluence 8.x it blows up w/ this NoSuchMethodException: Caused by: java.lang.NoSuchMethodException: com.opensymphony.xwork2.ActionContext.setParameters(java.util.Map)     at java.base/java.lang.Class.getMethod(Class.java:2108)     at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.getACStruts2Method(ActionContextStruts2AndWWCompat.java:200)     at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.<init>(ActionContextStruts2AndWWCompat.java:44)     at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:34)     ... 432 more com.atlassian.confluence.api.service.exceptions.ServiceException: ActionContext couldn't be initialized. {{}}

            Hi everyone, 

            6a66c94f366a , as mentioned by b3fc5ad4bc3b , please see Struts 2 upgrade section for the same. 

            deb4b4fcfa81 Thanks for mentioning the package name discrepancy, we have fixed the documentation. We recommend bundling the compat-lib and initialising it manually using spring config. 

            Thanks, 
            Ganesh

            Ganesh Gautam added a comment - Hi everyone,  6a66c94f366a , as mentioned by b3fc5ad4bc3b , please see Struts 2 upgrade section for the same.  deb4b4fcfa81 Thanks for mentioning the package name discrepancy, we have fixed the documentation. We recommend bundling the compat-lib and initialising it manually using spring config.  Thanks,  Ganesh

            The ServletActionContextCompatManager is not available to 

            com.atlassian.plugins.osgi.javaconfig.OsgiServices#importOsgiService as used by plugins that are doing Spring Java configuration instead of Atlassian Spring Scanner or whatever else.

            See: https://developer.atlassian.com/server/framework/atlassian-sdk/spring-java-config-spring-scanner/

            This means you will get a ServiceUnavailableException from your plugin code that uses ServletActionContextCompatManager if you do this in your Spring Java configuration class:

             

            @Bean
            public ServletActionContextCompatManager servletActionContextCompatManager() {
                return importOsgiService(ServletActionContextCompatManager.class);
            }
            

            So, for now, hardcode the ServletActionContextCompatManager constructor like this in your Spring Java configuration:

             

            @Bean
            public ServletActionContextCompatManager servletActionContextCompatManager() {
                return new ServletActionContextCompatManager();
            }
            

             

             

            Ture Hoefner {Appfire} added a comment - The ServletActionContextCompatManager is not available to  com.atlassian.plugins.osgi.javaconfig.OsgiServices#importOsgiService as used by plugins that are doing Spring Java configuration instead of Atlassian Spring Scanner or whatever else. See: https://developer.atlassian.com/server/framework/atlassian-sdk/spring-java-config-spring-scanner/ This means you will get a ServiceUnavailableException from your plugin code that uses ServletActionContextCompatManager if you do this in your Spring Java configuration class:   @Bean public ServletActionContextCompatManager servletActionContextCompatManager() {     return importOsgiService(ServletActionContextCompatManager.class); } So, for now, hardcode the ServletActionContextCompatManager constructor like this in your Spring Java configuration:   @Bean public ServletActionContextCompatManager servletActionContextCompatManager() {   return new ServletActionContextCompatManager(); }    

            The instructions for the compat lib have a mistake in them related to which package one of the compat classes is in.

            They say to replace com.opensymphony.webwork.ServletActionContext you should use:

            com.atlassian.confluence.compat.struts2.actioncontext.ServletActionContextCompatManager

            but it is:

            com.atlassian.confluence.compat.struts2.servletactioncontext.ServletActionContextCompatManager

             

            I cracked open confluence-compat-lib-1.5.0.jar to find out where ServletActionContextCompatManager because I could not believe my eyes when my IDE said that class did not exist.  It does exist, just not where the doc says it does.

            Ture Hoefner {Appfire} added a comment - The instructions for the compat lib have a mistake in them related to which package one of the compat classes is in. They say to replace com.opensymphony.webwork.ServletActionContext you should use: com.atlassian.confluence.compat.struts2. actioncontext .ServletActionContextCompatManager but it is: com.atlassian.confluence.compat.struts2. servletactioncontext .ServletActionContextCompatManager   I cracked open confluence-compat-lib-1.5.0.jar to find out where ServletActionContextCompatManager because I could not believe my eyes when my IDE said that class did not exist.  It does exist, just not where the doc says it does.

            Joshua Yamdogo @ Adaptavist added a comment - They included instructions for the compat lib here: https://confluence.atlassian.com/doc/struts-2-upgrade-1155473773.html#Struts2upgrade-30sept

            Hi Ganesh, 

            Still not clear for us if the compatibility layer will be included in Confluence 8 or if it will be part of a separate library. In any case please indicate where and how can we use it.

            Regards, 

            Pablo

            Pablo Gallego _Appfire_ added a comment - Hi Ganesh,  Still not clear for us if the compatibility layer will be included in Confluence 8 or if it will be part of a separate library. In any case please indicate where and how can we use it. Regards,  Pablo

              8d92d18aad77 Bhanu Darisi (Inactive)
              ggautam Ganesh Gautam
              Affected customers:
              8 This affects my team
              Watchers:
              16 Start watching this issue

                Created:
                Updated:
                Resolved: