Skip to end of metadata
Go to start of metadata

Available:

Confluence 2.2 and later

Changed:

In Confluence 3.3 and later, plugins can define their own contexts using the KeyedBandanaContext interface.

 

There are three main persistence APIs which are used in Confluence. Each of these APIs is discussed on this page:

  1. Bandana – XML persistence, easy to use in plugins.
  2. Hibernate – database persistence, difficult to extend.
  3. Content properties – database persistence for properties associated with a piece of Confluence content.

Because Bandana is the primary persistence API used by plugin developers, it will be covered in more detail than the other two APIs.

Bandana

Bandana is an Atlassian framework for persistence of arbitrary Java objects. The concepts used in Bandana are very simple:

  • Bandana stores data in contexts. Confluence defines one global context and one context per space. The relevant class is ConfluenceBandanaContext. In Confluence 3.3 and later, plugins can define their own contexts using the KeyedBandanaContext interface.
  • Each context stores key-value pairs. The key is a String and the value can be any Object. It should typically implement Serializable. If the key or value types are defined within a plugin, the class should have a no-argument constructor to avoid class loading issues.

Based on this design, the BandanaManager has methods for storing and retrieving values from a context by key:

  • void setValue(BandanaContext context, String key, Object value) – store a value against a key in the Bandana context.
  • Object getValue(BandanaContext context, String key) – get a key's value from the Bandana context. Returns null if no matching context and key exists.
  • void removeValue(BandanaContext context, String key) – remove a key and value from the Bandana context. (Available in Confluence 3.3 and later.)
  • Object getValue(BandanaContext context, String key, boolean lookUp) – same as above, except if lookUp is true and the context is a space context, this method will also check the global context if no matching key is found in the space context.
  • Iterable<String> getKeys(BandanaContext context) – provides an iterable to allow enumeration of all keys within a context. (Available in Confluence 3.3 and later.)

For plugins which use a context not provided by the application, we recommend that you use a context for your Bandana values that includes the full package name of your plugin. For example, a theme plugin might use a context like org.acme.confluence.mytheme.importantPreference.

Serialization

By default, Bandana uses XStream to convert objects into XML for storage. It is however possible to provide your own method of serialisation. If your BandanaContext implements the BandanaSerializerFactory interface (available in Confluence 3.3 and later) it will be used to create an serialiser to serialise and deserialise your objects.

Data storage

Prior to Confluence 2.3, this XML was written to the filesystem in the Confluence home directory. The file config/confluence-global.bandana.xml stores the global context, and there is a file config/spaceKey/confluence-space.bandana.xml with the configuration for each space. In Confluence 2.3 and later, Bandana data is written to the BANDANA table in the database, with three columns for context, key and a serialized value.

Getting access to BandanaManager

To get access to the BandanaManager from your plugin code, normally you only need to include a private BandanaManager field with an associated constructor parameter. Spring will construct your object and pass in the required component.

Hibernate

Confluence uses the open source persistence framework Hibernate. Confluence 2.2.x uses Hibernate version 2.1.8.

Each object to be persisted has a *.hbm.xml file which sits in the same directory as the associated class in the Confluence web application. For example, Label.class has an associated Label.hbm.xml which describes how label objects will be persisted. The particular details vary from class to class, but typically include:

  • the database table used to hold the data (Confluence bootstrap creates these tables if they do not exist)
  • the column names and mappings to class attributes
  • any special queries used for functionality in Confluence (for example, to retrieve a list of personal labels)

All this data is expressed in the standard Hibernate mapping format. In some cases, there is a single mapping file for all subclasses of a particular class. For example, ContentEntityObject.hbm.xml includes mappings for pages, news, mail and space descriptions.

The Hibernate mapping files are listed in mappingResources bean in applicationContext.xml. The list of Hibernate types (and mapping files) cannot be extended dynamically by plugins.

Although it might be possible to extend Confluence's database through Hibernate, this is not recommended. There are a few downfalls with extending our Hibernate configuration:

  1. You need to maintain your forked copy of the Hibernate mappings file against each new version of Confluence.
  2. Your new Hibernate objects will not be protected from (or necessarily upgraded to) any changes we make in the schema in future versions.
  3. Unless you really understand our code, something weird will happen.

Avoid using Confluence's database to store custom data – use content properties or Bandana instead.

Content properties

Another form of persistence, content properties are key-value pairs associated with a ContentEntityObject and stored in the database.

Content properties are accessed through the ContentPropertyManager like this:

You should get the ContentPropertyManager and PageManager injected into your macro, servlet, etc. using the techniques outlined on Accessing Confluence Components from Plugin Modules (also demonstrated in the section above).

21 Comments

  1. There is another issue for plugins, specifically if they have been installed by being uploaded (or more recently, via the Plugin Repository). If you try to store an object which is defined in the plugin, it will fail when you try to retrieve it because the BandanaManager was created by a ClassLoader which has no knowledge of the plugin's classes.

    The solution/workaround is to convert your object to an XML string via XStream before you stick it in Bandana. Something like this:

    The key is to set the class loader to the plugin's class loader. You'll also want to add a null-check on any getValue response...

    1. Is this the same issue as CONF-6655@JIRA?

      I think someone was looking at it a while back. I'll check on the status, and update the issue if it has changed.

      1. Yep, same problem. Actually, it's deserialisation that's the problem, not the initial storing of the object. I don't think this will be resolved any time soon - there was a large debate about plugin classloaders which resulted in the current system.

        1. Mike and I have both recently looked into the problem, and came to the same conclusion. Essentially that XStream can't be configured enough to solve this problem without forking XStream.

          XStream (as far as I can tell) needs to be modified use the classloader of the calling class, so if a plugin is deserializing then that plugin's classloader needs to be used. A temporary solution is to store as JDK classes (or at least classes which are on the webapp's classpath).

    2. In David Peterson's example code, the method name is toXML() (upper case XML) and not toXml()! This stumped me for about 15 minutes until I realised that. (The same goes for the fromXML() method later on.)

      1. Sorry about that - must have been coding from memory (smile)

  2. Prior to Confluence 2.3, this XML was written to the filesystem in the Confluence home directory. The file config/confluence-global.bandana.xml stores the global context, and there is a file config/spaceKey/confluence-space.bandana.xml with the configuration for each space. In Confluence 2.3 and above, Bandana data is written to the BANDANA table in the database, with three columns for context, key and an XML-serialized value.

    Does this mean that the space scope is now lost, so that the key a.b.c when looked up from a global BandanaContext is the same as when looked up from a space BandanaContext? (... or do you prefix the key with the space key / a unique global key and a separator ...)

    1. No, internally it will work exactly as before, only the storage format has changed.

      A space or global scope is represented by the context column. For a space scope, the context value will be the space key; for a global scope, the context value will be SQL NULL.

  3. I have a question regarding hibernate as we want to integrate our own persistent objects with Confluence and we don't want to use a separate instance of hibernate's configuration.

    The question is do I have any possibility to add my own mapping except for applicationContext.xml modification? I was trying to add it by calling

    but this didn't help.

    1. Hibernate is not extensible in Confluence. I'll update the documentation above to make that clear.

      The preferred mechanisms for plugins are documented above: Bandana and content PropertySets. We'll be supporting a full ORM called ActiveObjects in the future, but this doesn't yet ship with the product.

  4. Where is there information on "Content properties"?  This page says that it can be used, but I cannot find information anywhere!

    1. Hi Tim, I believe it is pointing to the "os_propertyentry" table.

    2. Sorry, my bad. I ran out of time while writing the initial version of this page.

      I've added some information above. Please let me know if there's any more specifics I should add about content properties.

  5. Anonymous

    How I can store some data, whitch I want share with other plugins in confluence? Thx.

    1. Any of the above options can be used to share data between plugins.

      1. Anonymous

        If I use Content properties for store some data, would I select by specific conditions? Because I want store information with date sign and then I need read this information by specific data range.

        1. Sorry, I'm not sure what you mean. It's probably best to post your question, with some sample code and a bit more context about what you're trying to achieve on Atlassian Answers. This isn't really the right place for development questions.

  6. Anonymous

    Just a note about Content Properties, appears based on the API that there is a 4096 character limit for storing strings (API references an Oracle limit)

    So if you are looking to store any piece of lengthy data you'll need to consider Bandana or ActiveObjects (which should probably be added to this page, supported since 4.3 i think)

  7. Anonymous

    Apologies if I am missing something obvious, but we need to 'import' the BandanaManager class into our Java code. But we can't do that without knowing its package name, right? I found it really hard to find the package name for BandanaManager: I had to really dig around until I found this page:

    https://docs.atlassian.com/atlassian-bandana/0.2.0/com/atlassian/bandana/BandanaManager.html

    Is there a better way to find this out?

  8. Anonymous

    What would be helpful is having a way of getting information in and out of the bandana directly. For example, a clean install of Confluence 5.2.3 is broken but can be fixed by a bandana change for which in this case would be useful to be able to put in the bandana directly..