• We collect Confluence feedback from various sources, and we evaluate what we've collected when planning our product roadmap. To understand how this piece of feedback will be reviewed, see our Implementation of New Features Policy.

      Dear Confluence team  

      As part of preparing our apps to the new XStream changes planned to be introduced by Confluence 7.10, we are facing one issue while doing the marshalling/unmarshalling process using the XStreamManagerCompat component.

       

      We're saving in Bandana an object containing a list of Label (com.atlassian.confluence.labels.Label). While doing toXml() directly with XStreamManagerCompat bandana we get this exception:

      this:

      String marshalledXml = xStreamManagerCompat.toXML(ourObjectContainingLabel);
      

      causes this:

      2020-12-09 10:48:01,241 ERROR [Caesium-1-1] [confluence.remotepublishing.macro.RemotePublishPageMacro] doExecute There was an error while publishing content2020-12-09 10:48:01,241 ERROR [Caesium-1-1] [confluence.remotepublishing.macro.RemotePublishPageMacro] doExecute There was an error while publishing contentcom.thoughtworks.xstream.converters.reflection.ObjectAccessException: Invalid final field com.atlassian.confluence.labels.Namespace.namespacePrefix at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.validateFieldAccess(PureJavaReflectionProvider.java:150) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:105) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.writeField(ReflectionConverter.java:78) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.visit(ReflectionConverter.java:59) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:114) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:68) at com.thoughtworks.xstream.converters.collections.CollectionConverter.marshal(CollectionConverter.java:47) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.writeField(ReflectionConverter.java:78) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.visit(ReflectionConverter.java:59) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:114) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.writeField(ReflectionConverter.java:78) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter$1.visit(ReflectionConverter.java:59) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:114) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:68) at com.thoughtworks.xstream.converters.collections.CollectionConverter.marshal(CollectionConverter.java:47) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:68) at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:51) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller.convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:46) at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy.marshal(ReferenceByXPathMarshallingStrategy.java:17) at com.thoughtworks.xstream.XStream.marshal(XStream.java:489) at com.thoughtworks.xstream.XStream.marshal(XStream.java:479) at com.thoughtworks.xstream.XStream.toXML(XStream.java:464) at com.atlassian.confluence.compat.setup.xstream.XStream111Compat.toXML(XStream111Compat.java:20) at com.atlassian.confluence.compat.setup.xstream.XStreamManagerCompat.toXML(XStreamManagerCompat.java:50) at com.comalatech.confluence.remotepublishing.confluence.BandanaPublishHistoryManager.saveBandanaPublishHistoryInfoSpaceConfigs(BandanaPublishHistoryManager.java:76)

      As you can see, the problem is related while the library handles the final attribute Namespace of the Label. The error is the same if we don't call to the XStreamManagerCompat and instead we rely in the XML marshalling of BandanaManager{{.}}

       

      Previous to this, we're using directly a XStream instance defining the StaxDriver and a  custom converter to be able to get rid of this exception and marshall/unmarshall the label object correctly. Code sample:

      XStream xStream = new XStream(new StaxDriver());
      // We were having problems with Label's Namespace, as it has a private constructor, so we need to convert from object to
      // XML and from XML to object manually
      xStream.registerConverter(new Converter() {
          @Override
          public boolean canConvert(Class aClass) {
             return aClass.equals(Namespace.class);
          }
          @Override
          public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {
             Namespace namespace = (Namespace)o;
             hierarchicalStreamWriter.startNode("namespacePrefix");
             hierarchicalStreamWriter.setValue(namespace.getPrefix());
             hierarchicalStreamWriter.endNode();
          }
          @Override
          public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) {
              hierarchicalStreamReader.moveDown();
              String namespacePrefix = hierarchicalStreamReader.getValue();
              hierarchicalStreamReader.moveUp();
              if (namespacePrefix.equals("my")){
                return Namespace.PERSONAL;
              }else if (namespacePrefix.equals("team")){
                return Namespace.TEAM;
              }else if (namespacePrefix.equals("global")){
                return Namespace.GLOBAL;
              }else {
                return Namespace.SYSTEM;
              }
           }
      });
      

      So we're kind of stuck here if from Confluence 7.10 we cannot add this kind of custom converters or configurations using the XStreamManagerCompat.

      • Are you plan to resolve this adding some way to customize the internal XStream?
      • Or in this case as the error is related to your internal Label model, maybe you can provide XStreamManagerCompat implementation already being compatible to marshall/unmarshall Confluence model.

       

      Let us know if you need anything more.

       

          Form Name

            [CONFSERVER-60576] Confluence 7.10 XStream Compat while saving

            Hi @Ganesh Gautam,

            Thank you for your reply. I have created a separate suggestion as per your recommendation CONFSERVER-60867.

            Regards,

            Ahmed

            Ahmed Alaghbari added a comment - Hi @Ganesh Gautam, Thank you for your reply. I have created a separate suggestion as per your recommendation  CONFSERVER-60867 . Regards, Ahmed

            Hi ahmed.alaghbari

            Thanks for using the new API and giving this amazing feedback. I am happy to see your app already taking advantage of our backward compatibility layer on new XStream.

            Lastly, I would recommend you to raise a separate suggestion, should you need API for mapper in future.

            Regards,

            Ganesh

            Ganesh Gautam added a comment - Hi ahmed.alaghbari Thanks for using the new API and giving this amazing feedback. I am happy to see your app already taking advantage of our backward compatibility layer on new XStream. Lastly, I would recommend you to raise a separate suggestion, should you need API for mapper in future. Regards, Ganesh

            Hi,

            First of all I'd like to thank @David Herrera Alonso for requesting the additional compatibility methods and for presenting the arguments for them well. Also, I'd like to thank @Ganesh Gautam for his timely response to the feedback and for making the suggested improvements available with the release of Confluence 7.10.0. Both your efforts have made it possible to make a much smoother transition into the new XStream version while still maintaining compatibility for those users still on the older version.

            With that being said, I would like to also propose further extension to confluence-compat-lib to cover the non-backward compatible XStream#getMapper method. To give you a bit of context, we are using the new registerConverter method. However, our custom Converter extends from XStream's MapConverter whose constructor requires a com.thoughtworks.xstream.mapper.Mapper. This was easily obtained by running XStream#getClassMapper on XStream 1.1.1 but since this was renamed on XStream1.4.14 to XStream#getMapper, we have resorted to using reflections to cater for both cases. It would be great if the compatibility library could expose this method to enable us to register convertors more efficiently and rely completely on XStreamManagerCompat ensuring better future proofing.

            @Component
            public class XStreamManagerCompat {
            ...
            /**
            * Retrieve the Mapper.
            *
            @return the default Mapper in XStream 1.4.x or the default ClassMapper in XStream 1.1.1.
            */
                public Mapper getMapper() {
                    delegate.getMapper();
            }
            }

             
            Thanks again for your help in making this transition as seamless as possible and for considering the suggestion brought forward in this comment.

            Ahmed.

            Ahmed Alaghbari added a comment - Hi, First of all I'd like to thank @David Herrera Alonso for requesting the additional compatibility methods and for presenting the arguments for them well. Also, I'd like to thank @Ganesh Gautam for his timely response to the feedback and for making the suggested improvements available with the release of Confluence 7.10.0. Both your efforts have made it possible to make a much smoother transition into the new XStream version while still maintaining compatibility for those users still on the older version. With that being said, I would like to also propose further extension to confluence-compat-lib to cover the non-backward compatible XStream#getMapper  method. To give you a bit of context, we are using the new registerConverter method. However, our custom Converter extends from XStream's MapConverter whose constructor requires a com.thoughtworks.xstream.mapper.Mapper . This was easily obtained by running XStream#getClassMapper on XStream 1.1.1 but since this was renamed on XStream1.4.14 to XStream#getMapper , we have resorted to using reflections to cater for both cases. It would be great if the compatibility library could expose this method to enable us to register convertors more efficiently and rely completely on XStreamManagerCompat  ensuring better future proofing. @Component public class XStreamManagerCompat { ... /** * Retrieve the Mapper. * *  @return the default Mapper in XStream 1.4.x or the default ClassMapper in XStream 1.1.1. */     public Mapper getMapper() {         delegate.getMapper(); } }   Thanks again for your help in making this transition as seamless as possible and for considering the suggestion brought forward in this comment. Ahmed.

            7.10.0 release has shipped with this change.

            Brendan McNamara added a comment - 7.10.0 release has shipped with this change.

            David, we are happy to convey that confluence-compat-lib 1.4.1 has been released with things discussed above. You can find full documentation for the same at the original XStream 1.4 upgrade doc.

             

            Thanks,

            Ganesh

            Ganesh Gautam added a comment - David, we are happy to convey that confluence-compat-lib 1.4.1 has been released with things discussed above. You can find full documentation for the same at the original  XStream 1.4 upgrade  doc.   Thanks, Ganesh

            Ganesh, really thanks again. It's been a pleasure to discuss with you about this .

             

            I see the new version of the compat lib is out, so we will start applying the changes to our apps .

             

            Regards,

            David

            David Herrera Alonso added a comment - Ganesh, really thanks again. It's been a pleasure to discuss with you about this .   I see the new version of the compat lib is out, so we will start applying the changes to our apps .   Regards, David

            Ganesh Gautam added a comment - - edited

            16fa4887a4db

            Right, we don't have XStream 1.1.1 dependency anymore in Confluence 7.10 rather just XStream 1.4.x. And it looks you have answered your question in next comment:

            calling to the marshal/unmarshal methods from both XStreamManagerCompat or directly ConfluenceXStreamManager will be safe against those errors.

            This is possible because we have introduced a layer in Confluence to handle the serialised formats from 1.1.1 through XStream 1.4.x using our custom wrappers. It doesn't mean we have XStream 1.1.1, rather we can handle 1.1.1 format  

            Thanks,

            Ganesh

            Ganesh Gautam added a comment - - edited 16fa4887a4db Right, we don't have XStream 1.1.1 dependency anymore in Confluence 7.10 rather just XStream 1.4.x. And it looks you have answered your question in next comment: calling to the marshal/unmarshal methods from both XStreamManagerCompat or directly ConfluenceXStreamManager will be safe against those errors. This is possible because we have introduced a layer in Confluence to handle the serialised formats from 1.1.1 through XStream 1.4.x using our custom wrappers. It doesn't mean we have XStream 1.1.1, rather we can handle 1.1.1 format   Thanks, Ganesh

            And one more question, sorry. Just to be sure and avoid possible future errors with our users. As the jump to 1.4.x version introduced some errors while unmarshalling data generated with 1.1.1, we understand the implementation you use in Confluence 7.10 has some mechanisms to avoid those errors. And therefore calling to the marshal/unmarshal methods from both XStreamManagerCompat or directly ConfluenceXStreamManager will be safe against those errors.

             

            And also, when 7.9 comes to EOL, is it planned to also deprecate the compat lib?

             

            Kind regards!

            David Herrera Alonso added a comment - And one more question, sorry. Just to be sure and avoid possible future errors with our users. As the jump to 1.4.x version introduced some errors while unmarshalling data generated with 1.1.1, we understand the implementation you use in Confluence 7.10 has some mechanisms to avoid those errors. And therefore calling to the marshal/unmarshal methods from both XStreamManagerCompat or directly ConfluenceXStreamManager will be safe against those errors.   And also, when 7.9 comes to EOL, is it planned to also deprecate the compat lib?   Kind regards!

            David Herrera Alonso added a comment - - edited

            Thanks again! We would go that way then . Once we see the new compat version is ready, we will implement the changes.

             

            One more question just to solve my technical/implementation curiosity

            I thought the internal dependencies of Confluence would change in 7.10 for using 1.4.x version of XStream. So then, the getXStream() method would retrieve an instance version of the XStream corresponding to the Confluence version. I mean, prior 7.10 it would always be an instance from 1.1.1. And in Confluence 7.10 onwards, the instance would be from 1.4.x (although the real object would not be the pure thoughtworks one but your implementation/wrapper).

            However, after your last comment and looking a bit to your internal interface it seems I was wrong and you're doing something different. Because if the getXStream() will always return a 1.1.1 version, that means Confluence 7.10 is still keeping 1.1.1 as internal dependency or retrieved with other OSGI bundle loader to not clash with the new 1.4.x lib version. 

             

            You don't have to answer me  because I'm entering into your implementation details and I know that it's "not my business". But as a tech guy, I couldn't resist my willing to know .

             

            Kind regards!!

            David Herrera Alonso added a comment - - edited Thanks again! We would go that way then . Once we see the new compat version is ready, we will implement the changes.   One more question just to solve my technical/implementation curiosity I thought the internal dependencies of Confluence would change in 7.10 for using 1.4.x version of XStream. So then, the getXStream() method would retrieve an instance version of the XStream corresponding to the Confluence version. I mean, prior 7.10 it would always be an instance from 1.1.1. And in Confluence 7.10 onwards, the instance would be from 1.4.x (although the real object would not be the pure thoughtworks one but your implementation/wrapper). However, after your last comment and looking a bit to your  internal interface  it seems I was wrong and you're doing something different. Because if the getXStream()  will always return a 1.1.1 version, that means Confluence 7.10 is still keeping 1.1.1 as internal dependency or retrieved with other OSGI bundle loader to not clash with the new 1.4.x lib version.    You don't have to answer me  because I'm entering into your implementation details and I know that it's "not my business". But as a tech guy, I couldn't resist my willing to know .   Kind regards!!

            David,

            The 4 changes you have mentioned are right. Let me also clarify the comment on Namespace converter. 
            As per me, you can just do the following: 

            YourClass.java
            public void doSomething() {
                confluenceXStreamCompat.getXStream().registerConverter(nameSpaceCoverter)
            }
            

            Since the confluenceXStreamCompat.getXStream() returns XStream type and that too for only 1.1.1, it will register your converter only on 1.1.1 XStream and not on 1.4.x XStream. You can then remove this Namespace converter tech-debt once 7.9 is out of life. Feel free to put a deprecation notice in your plugin's codebase.  
            For all other converters which are not workaround of something caused by XStream bugs, you can directly register with two new methods we are introducing in ConfluenceXStreamCompat. These two methods will register your converter on both versions of XStream in underlying layer.

            Thanks for highlighting the typo , it was indeed 7.10, I have corrected my previous comment.

            Many thanks,

            Ganesh

            Ganesh Gautam added a comment - David, The 4 changes you have mentioned are right. Let me also clarify the comment on Namespace converter.  As per me, you can just do the following:  YourClass.java public void doSomething() { confluenceXStreamCompat.getXStream().registerConverter(nameSpaceCoverter) } Since the confluenceXStreamCompat.getXStream() returns XStream type and that too for only 1.1.1, it will register your converter only on 1.1.1 XStream and not on 1.4.x XStream. You can then remove this Namespace converter tech-debt once 7.9 is out of life. Feel free to put a deprecation notice in your plugin's codebase.   For all other converters which are not workaround of something caused by XStream bugs, you can directly register with two new methods we are introducing in ConfluenceXStreamCompat . These two methods will register your converter on both versions of XStream in underlying layer. Thanks for highlighting the typo , it was indeed 7.10, I have corrected my previous comment. Many thanks, Ganesh

              ggautam Ganesh Gautam
              16fa4887a4db David Herrera Alonso
              Votes:
              1 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated:
                Resolved: