XStream 1.4 upgrade

Preparing for Confluence 7.10

On this page

Still need help?

The Atlassian Community is here for you.

Ask the community

Confluence is working on upgrading XStream from 1.1.1 to 1.4.13. XStream is a library to serialize objects to XML and back again.

Update from  

confluence-compat-lib 1.4.1 has been released!

To cater plugins requiring access to converters/alias as requested by CONFSERVER-60576 - Getting issue details... STATUS , we have added three more methods in XStreamManagerCompat namely getXStream(), registerConverter(converter, priority) and alias(alias, class). These methods allow a plugin vendor to hook in XStream converters and aliases, regardless of the Confluence version, automatically. Ideally plugins using Bandana wouldn't need to use any of this, and these methods are only meant for plugins which were using XStream themselves in custom ways. Please see the current structure of the class in the code example below:

/** 
* This class lets plugins to be compatible with all supported versions of Confluence for XStream. 
* Since 7.10 Confluence uses newer XStream and has added methods in XStreamManager. 
* XStreamManagerCompat is responsible for choosing between XStreamManager or creating a new XStream(Confluence 7.9 and prior) 
*/ 
@Component 
public class XStreamManagerCompat { 
    /** 
    * Serialize an object to a pretty-printed XML String. 
    */ 
    public String toXML(Object obj) { 
        return delegate.toXML(obj); 
    } 


/** 
* Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case * of an exception. 
*/ 
public void toXML(Object obj, Writer writer) { 
    delegate.toXML(obj, writer); 
    } 


/** 
* Deserialize an object from an XML String. 
*/ 
public Object fromXML(String xml) { 
    return delegate.fromXML(xml); 
    } 


/** 
* Deserialize an object from an XML Reader. 
*/ 
public Object fromXML(Reader reader){ 
    return delegate.fromXML(reader); 
    } 


/** 
* Provides a way to access 1.1.1 XStream object 
* Supposed to be removed when Confluence 7.9 is EOL. 
* @return XStream 1.1.1 equivalent object. 
*/ 
public XStream getXStream() { 
    return delegate.getXStream(); 
    } 


/** 
* Register a converter with the appropriate priority. 
* @param XStream converter to register 
* @param priority Priority to be used in XStream 
*/ 
public void registerConverter(Converter converter, Integer priority) {
    delegate.registerConverter(converter, priority); 
    } 


/** 
* Alias a Class to a shorter name to be used in XML elements. 
* @param name Short name 
* @param type Type to be aliased 
*/ 
public void alias(String name, Class<?> type) { 
    delegate.alias(name, type); } 
    }


Update from  

Confluence 7.10.0-rc1 is now available (Release Candidate 1). The release candidate (and the beta release before it) contains the XStream upgrade, and we strongly recommend you test your plugin, as you may need to make changes, as described below. 

Update from  

We've made the following changes in the 7.10.0-xstream-14-m03 milestone

  • We have added one more method in ConfluenceXStreamManager to allow a plugin to use the plugin bundle Classloader while serialisations and avoid ClassCastExceptions arising from mismatched class loaders. In simple words, Confluence Core uses an uberClassLoader in its XStream, but plugin classes are generally loaded by a BundleClassLoader, once an XML XStream String is de-serialised it can result in a Class object which doesn't belong to the plugin's classloader and hence a ClassCastException. A plugin can utilise the following method from ConfluenceXStreamManager, to do-so:

    /**
    * Allows to use plugin OSGi classloaders and avoid ClassCastException in case of plugin re-installations.
    *
    * @param classLoader classLoader to use for serialization/deserialization in XStream
    * @return ConfluenceXStream based on classLoader
    */
    ConfluenceXStream getPluginXStream(ClassLoader classLoader);
  • To use above API in our old example, the usage would look like:

    public class ExampleClass {
        private final ConfluenceXStream pluginXStream;
    	 @Autowired
    	public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) {
            //Note that classLoader being used below is plugin's classloader
            this.pluginXStream = confluenceXStreamManager.getPluginXStream(getClass().getClassLoader());
            //Also noe that pluginXStream is directly made a field and can just be created once in plugin lifecycle.
         }
    
        private SomeObject toSomeObject(String someString) {
            return (SomeObject) pluginXStream.toXML(someString);
        }
    }
  • confluence-compat-lib 1.4.0 has been released to provide Backward-compatible code for plugins which want to keep a single version across all supported Confluence versions.
    Plugins can utilise component  com.atlassian.confluence.compat.setup.xstream.XStreamManagerCompat plugins. It can be initiated by way of Spring or manually with no-arg constructor XStreamManagerCompat(). See following for its signature: 

    /**
     * This class lets plugins to be compatible with all supported versions of Confluence for XStream.
     * Since 7.10 Confluence uses newer XStream and has added methods in XStreamManager.
     * XStreamManagerCompat is responsible for choosing between XStreamManager or creating a new XStream(Confluence 7.9 and prior)
     */
    @Component
    public class XStreamManagerCompat {
    /**
         * Serialize an object to a pretty-printed XML String.
         */
        public String toXML(Object obj) {
            return delegate.toXML(obj);
        }
    
        /**
         * Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case
         * of an exception.
         */
        public void toXML(Object obj, Writer writer) {
            delegate.toXML(obj, writer);
        }
    
        /**
         * Deserialize an object from an XML String.
         */
        public Object fromXML(String xml) {
            return delegate.fromXML(xml);
        }
    
        /**
         * Deserialize an object from an XML Reader.
         */
        public Object fromXML(Reader reader){
            return delegate.fromXML(reader);
        }
    }
  • Plugins can use above compat API by referencing following example: 

    pom.xml snippet
    <dependency>
        <groupId>com.atlassian.confluence.compat</groupId>
        <artifactId>confluence-compat-lib</artifactId>
        <version>1.4.0</version>
    </dependency>
    spring-component.xml
    <bean id="xStreamManagerCompat" class="com.atlassian.confluence.compat.setup.xstream.XStreamManagerCompat"/>
    Example compatibility code
    public class ExampleClass {
        private final XStreamManagerCompat xStreamManagerCompat;
    	 @Autowired
    	public ExampleClass(@Qualifier("xStreamManagerCompat") final XStreamManagerCompat xStreamManagerCompat) {
            this.xStreamManagerCompat = xStreamManagerCompat;
        }
    
    
        private SomeObject toSomeObject(String someString) {
            return (SomeObject) xStreamManagerCompat.toXML(someString);
        }
    }

Update from  

Confluence is working on upgrading XStream from 1.1.1 to 1.4.13. XStream is a library to serialize objects to XML and back again.

Unfortunately, XStream 1.4.x breaks compatibility with the old format of stored data and ends up throwing ConversionException errors while un-marshalling data. To support existing data conversion, we are adding a layer of backward compatibility to support existing data in Bandana.

In most cases plugins will not need to use XStream by themselves for Bandana storage, but we suspect due to historical reasons, people might be creating their own XStream instances (as in the example mentioned in this Jira comment). Classes with their own "new XStream()"   instance will not benefit from our 1.1.1 compatible XStream. 

We would also like to highlight that Bandana already contains XStream marshalling/unmarshalling under the hood, so if plugins are also handling marshals, it will cause double marshalling/unmarshalling which is unnecessary. We strongly recommend you move away from this approach and rely only on Bandana Manager's unmarshallers. Here are two examples of double marshalling/unmarshalling: 

Example#1 for bad double marshalling
private static final BandanaContext BANDANA_CONTEXT = new ConfluenceBandanaContext();
private final XStream xStream = new XStream();
public SomeObject getSomeObject() {
    Object aStringObject = this.bandanaManager.getValue(BANDANA_CONTEXT, "some-key");
    if (aStringObject instanceof String) {
       try {
            Object someObject = this.xStream.fromXML((String)aStringObject);
            if (someObject instanceof SomeObject) {
                return (SomeObject) someObject;
            }
       } catch (ConversionException var3) {
            LOG.error("couldn't parse data");
            throw new Exception("Some Exception");
        }
    }
}
 
public void setSomeObject(SomeObject someObject) {
    this.bandanaManager.setValue(BANDANA_CONTEXT, "some-key", this.xStream.toXML(someObject));
}
Example#2 for unnecessary double unmarshalling for sake of backward compatibility
private DesignConfiguration getConfig() {
	DesignConfiguration config = null;
	try {
		Serializable serializable = (Serializable) bandanaManager.getValue(confluenceBandanaContext, BD_KEY);
		if (serializable instanceof DesignConfiguration) {
			config = (DesignConfiguration) serializable;
		} else if (serializable instanceof String) {
			config = (DesignConfiguration) xStream.fromXML((String) serializable);
		}

	} catch (Exception e) {
		logger.warn("Error converting  DesignConfiguration", e);
	}
	return config;
}

For cases where plugins do need to use XStream for some reason, we are exposing Confluence managed XStream instances through ConfluenceXStreamManager. Following classes will help you start:

  • com.atlassian.confluence.setup.xstream.ConfluenceXStream

  • com.atlassian.confluence.setup.xstream.ConfluenceXStreamManager  (plugin accessible spring bean with id "xStreamManager")

  • com.atlassian.confluence.impl.xstream.DefaultConfluenceXStreamManager 

  • com.atlassian.confluence.setup.xstream.XStreamManager (existing class which is now marked as deprecated in favour of DefaultConfluenceXStreamManager)

Spring bean "xStreamManager" provides you ConfluenceXStream which is a wrapper around XStream but with the backward compatibility layer. confluenceXStream.toXML(obj) achieves you same results as xstream.toXML(obj). See:

public interface ConfluenceXStream {
    /**
     * Serialize an object to a pretty-printed XML String.
     *
     * @throws XStreamException if the object cannot be serialized
     */
    String toXML(Object obj);

    /**
     * Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case
     * of an exception.
     *
     * @throws XStreamException if the object cannot be serialized
     */
    void toXML(Object obj, Writer writer);

    /**
     * Deserialize an object from an XML String.
     *
     * @throws XStreamException if the object cannot be deserialized
     */
    Object fromXML(String xml);

    /**
     * Deserialize an object from an XML Reader.
     *
     * @throws XStreamException if the object cannot be deserialized
     */
    Object fromXML(Reader reader);
}

What you need to do

  • Check whether there is double marshalling in Bandana (as mentioned in above examples). If there is, you'll need to avoid this, and add migration tasks to move away from this situation. 
    • If you can get rid of double-marshalling, you might need to introduce a migration layer which runs when your plugin starts up. This could be done by leveraging  ConfluenceXStream's toXML(obj) to create the right bandanaValue from String and then do the following:

      private DesignConfiguration getConfig() {
      	DesignConfiguration config = null;
      	try {
        		config = (DesignConfiguration) bandanaManager.getValue(confluenceBandanaContext, BD_KEY);
      	} catch (Exception e) {
      		logger.warn("Error converting DesignConfiguration", e);
      	}
      	return config;
      }
    • If you can't get rid of double-marshalling, please use confluenceXStreamManager.getConfluenceXStream.toXML(obj) instead of your own xStream object. For example:

      public class ExampleClass {
          private final ConfluenceXStreamManager confluenceXStreamManager;
      	 @Autowired
      	public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) {
              this.confluenceXStreamManager = confluenceXStreamManager;
          }
      
      
          private DesignConfiguration getConfig() {
              DesignConfiguration config = null;
              try {
                  Serializable serializable = (Serializable) bandanaManager.getValue(confluenceBandanaContext, BD_KEY);
                  if (serializable instanceof DesignConfiguration) {
                      config = (DesignConfiguration) serializable;
                  } else if (serializable instanceof String) {
                      config = (DesignConfiguration) confluenceXStreamManager.getConfluenceXStream().toXML((String) serializable);
                  }
              } catch (Exception e) {
                  logger.warn("Error converting DesignConfiguration", e);
              }
              return config;
          }
      }

      Note: confluenceXStreamManager.getConfluenceXStream() has to be done every time as confluenceXStream gets reset on each PluginInstall / PluginUninstall event. 

  • If your plugin is just relying on the BandanaManager API without touching XStream itself, this plugin is safe, there's no need to worry. Backward compatibility will come for free.
  • If your plugin requires XStream to do some custom stuff, use confluenceXStreamManager.getConfluenceXStream.toXML(obj) instead of xStream.toXML(obj). See:

    public class ExampleClass {
        private final ConfluenceXStreamManager confluenceXStreamManager;
    	 @Autowired
    	public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) {
            this.confluenceXStreamManager = confluenceXStreamManager;
        }
    
    
        private SomeObject toSomeObject(String someString) {
            return (SomeObject) confluenceXStreamManager.getConfluenceXStream().toXML(someString);
        }
    }

Security changes required to use XStream from Confluence XStream API

  • XStream 1.4.x brings its own security module, as opposed to the past where we had our own XStreamSecurityClassFilter . This security module required to work in allow-list mode is not enabled by default.
  • We have introduced a xstream.allowlist.enable system property which allows customers to enable the XStream allow list and block everything by default.

  • Note: To help you to make your plugin work with the security allowlist, the xstream.allowlist.enable system property is on by default in the EAP milestone.
  • Plugins can use a new xstream-security module in atlassian-plugin.xml to configure Confluence's XStreams with types, regex or wildcards. We strongly recommend plugins implement this to avoid XStream restrictions if a customer goes into more strict mode. See: 

    <xstream-security key = "xstream-set" name="Some XStream allowlist set">
        <type>com.atlassian.test.ExampleClass</type>
        <type>com.atlassian.test.AnotherExampleClass</type>
        <regex>com.atlassian.example.*</regex>
        <wildcard>com.some.package.**</wildcard>
    </xstream-security>
  • These types, regex or wildcards are in line with what XStream states in their documentation, See https://x-stream.github.io/security.html#example for more information.
  • XStream 1.5 will default to allow-list security behaviour. This could come in the next XStream upgrade.

Upcoming changes

We're still working on this upgrade. A future EAP milestone will:

  • Introduce Upgrade task to migrate Bandana data in 1.1.1 format to 1.4.x data format
  • Ensure CCMA understands server 1.1.1 format and creates export for cloud using 1.4.x format
Last modified on Dec 15, 2020

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.