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.

            [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

            Hi Ganesh,

            This one appears as finished now, where and how can we implement the compatibility library? Also against which Confluence 8 EAP should we test it?

            Regards, 

            Pablo Gallego

            Appfire

            Pablo Gallego _Appfire_ added a comment - Hi Ganesh, This one appears as finished now, where and how can we implement the compatibility library? Also against which Confluence 8 EAP should we test it? Regards,  Pablo Gallego Appfire

            Thank you Ganesh. 

            Heads up to folks who use the "eap" label in Atlassian's docker repository for maintaining their test Confluence servers:  the current "eap" label points to the 8.0.0-struts-m39 (the struts release), not to the latest non-breaking EAP release (8.0.0-m41).

            https://hub.docker.com/r/atlassian/confluence-server/tags?page=1&name=eap

            The mistake that has been made (IMO) is that Atlassian put the breaking release into the "eap" label.  The non-breaking release (8.0.0-m41) should be in the "eap" label.

            Use "eap-struts" or some other docker repository label that people would explicitly/intentionally use to get/test the breaking release.  As it is now, anyone who has been using the "eap" label for their docker-deployed test EAP server has the broken one, not the unbroken one.

            Ture Hoefner {Appfire} added a comment - Thank you Ganesh.  Heads up to folks who use the "eap" label in Atlassian's docker repository for maintaining their test Confluence servers:  the current "eap" label points to the 8.0.0-struts-m39 (the struts release), not to the latest non-breaking EAP release (8.0.0-m41). https://hub.docker.com/r/atlassian/confluence-server/tags?page=1&name=eap The mistake that has been made (IMO) is that Atlassian put the breaking release into the "eap" label.  The non-breaking release (8.0.0-m41) should be in the "eap" label. Use "eap-struts" or some other docker repository label that people would explicitly/intentionally use to get/test the breaking release.  As it is now, anyone who has been using the "eap" label for their docker-deployed test EAP server has the broken one, not the unbroken one.

            Hi deb4b4fcfa81 ,

            8.0.0-struts-m39 is specifically a Struts breaking change EAP and the whole point of the EAP is to allow developers to provide early feedback and also see the changes happening. Even though the priority is lower with respect to other Struts items, I would want to assure you that its a struts-blocker as labelled in the ticket.
            We have 8.0.0-m41 out for EAP which doesn't have Struts if thats what you are looking for. 

            This issue is in progress and there are internal PRs under review for the same. We are targeting to release it pretty soon.

            Thanks,
            Ganesh

            Ganesh Gautam added a comment - Hi deb4b4fcfa81 , 8.0.0-struts-m39 is specifically a Struts breaking change EAP and the whole point of the EAP is to allow developers to provide early feedback and also see the changes happening. Even though the priority is lower with respect to other Struts items, I would want to assure you that its a struts-blocker  as labelled in the ticket. We have 8.0.0-m41 out for EAP which doesn't have Struts if thats what you are looking for.  This issue is in progress and there are internal PRs under review for the same. We are targeting to release it pretty soon. Thanks, Ganesh

            Hello Atlassian,

            Now that the Confluence EAP version is 8.0.0-struts-m39 some of our plugins fail to deploy on our Confluence EAP server.

            Please revert the EAP version to the old Confluence version that doesn't break xwork by requiring a non-backwards compat migration to struts.  You have released a Confluence version that is backwards incompatible in an area that plugins are going to be using some TBD backwards-compat API/library (the subject of this bug)... until this bug is fixed/delivered we have no way to write backwards-compat code that is deployed on Confluence EAP.  We have a Confluence EAP that we cannot use/test.

            Until the TBD backwards-compat API/library is delivered you probably shouldn't have your Confluence EAP version out there in the world.  We can't test it because we are not forking our plugin code into a branch that is not backwards-compat.  That is a non-starter.  Therefore, if any other problems start piling up w/ the EAP release we are not going to be see and report those other problems.  Our plugins don't even deploy on EAP. 

            The EAP release holds no value for our plugins that are not un-deployable on the EAP release.

            PLEASE, provide a backwards-compat solution for the xwork --> strust migration and THEN release an EAP version of Confluence that breaks xwork.  At that point, the plugin developers can refactor to use the new compat lib and test the Confluence EAP with the plugin code that is going to be deployed by our customers.

            Thanks.

            Ture Hoefner {Appfire} added a comment - Hello Atlassian, Now that the Confluence EAP version is 8.0.0-struts-m39 some of our plugins fail to deploy on our Confluence EAP server. Please revert the EAP version to the old Confluence version that doesn't break xwork by requiring a non-backwards compat migration to struts.  You have released a Confluence version that is backwards incompatible in an area that plugins are going to be using some TBD backwards-compat API/library (the subject of this bug)... until this bug is fixed/delivered we have no way to write backwards-compat code that is deployed on Confluence EAP.  We have a Confluence EAP that we cannot use/test. Until the TBD backwards-compat API/library is delivered you probably shouldn't have your Confluence EAP version out there in the world.  We can't test it because we are not forking our plugin code into a branch that is not backwards-compat.  That is a non-starter.  Therefore, if any other problems start piling up w/ the EAP release we are not going to be see and report those other problems.  Our plugins don't even deploy on EAP.  The EAP release holds no value for our plugins that are not un-deployable on the EAP release. PLEASE, provide a backwards-compat solution for the xwork --> strust migration and THEN release an EAP version of Confluence that breaks xwork.  At that point, the plugin developers can refactor to use the new compat lib and test the Confluence EAP with the plugin code that is going to be deployed by our customers. Thanks.

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

                Created:
                Updated:
                Resolved: