Plugin Dependancies and Classloader Concerns

Confluence's plugin manager doesn't currently deal with the following well, hopefully we can address this. Some plugins have to go into WEB-INF/lib and not home/plugins, these are commonly component plugins, plugin class dependancies, and plugins that use bandana.

If any other reasons for plugins not to be installable into home/plugins, then please add them.

Velocity Caching

Sometimes the velocity caches aren't being cleared. If anyone knows of a reliable way to reproduce this please please the routine here so I can have a go at tracking down the bug.

Resource's missing Last Modified HTTP header

With resources missing the last modified header resources are being constantly pulled down and not cached - which isn't a good thing. So I suggest modifying the PluginResourceDownload.servePluginResource(FileServerServlet,HttpServletResponse,String,String) method to look at the plugin to find out the date it was installed (see DefaultPluginManager.addPlugin for where the info should be set).

After a discussion with Mike Cannon-Brookes, a more general class should be added alongside PluginInformation, called something like PluginInstallationStatus which would hold plugin exceptions / error information (for a much nicer error UI) as well as storing the date the plugin was installed.

Resource's missing Size HTTP header

This could be done by modifying PluginResourceDownload to cache the file and then stream it out - however this wouldn't be a good idea if memory is an issue. This may just be a header which we let slide.

Component Plugins

It appears that when a component plugin is uninstalled, its Spring reference is not removed completely and fatal events happen when either an uninstalled component is accessed, or the component is reinstalled. The only solution is to restart the webapp and then install new plugin.

This problem appears to have been an issue with the disabled() method not being called when a plugin with components is uninstalled. A work around for this is to disable the plugin before uninstalling it (and enable it again after reinstallation).

Plugin Class Dependancies

There are plugins (eg: Utilities) that are used by other plugins, and there are cases where two plugins want to use different versions of the same library. To make the classes from one plugin available to another, the shared plugin must currently be installed in WEB-INF/lib. There is currently no way to have two plugins depending on different versions of the same library.

A solution could be to add something like the following to the atlassian-plugin.xml file:

<dependancies>
  <atlassian-plugin key="dependant.plugin.key" />
  <library>/lib/library.jar</library>
<dependancies>

In this example, the jar "libary.jar" would be in the plugins directory (as would be the preferred method to hel). The plugin's classloader would be modified to hunt for class's in the following locations:

  1. The plugin jar file directly
  2. Any 'jar' resources declared in atlassian-plugin.xml (bundled in the plugin's jar)
  3. Any other plugins declared as dependencies in atlassian-plugin.xml (by accessing that plugin's classloader)
  4. The default Confluence classloader (WEB-INF/lib/* + WEB-INF/classes)

This solves both the problem of making home/plugins plugins accessible to other plugins, and the problem of two plugins using two different versions of the same jar. However with classes including their libraries as resources it may bloat the jar files.

After hacking around for a couple of hours (hitting a few dead ends) and having a few discussions with Jonathan Nolen and Jason Dillon it has been suggested that the Classworlds project on Codehaus appears to facilitate what we want. It would obviously require migration of tonic over to using the library, but I plan to investigate it further over the weekend to see exactly what would be invovled.

I've essentially picked up the following points (feel free to correct if I misinterpret):

  • There is a single ClassWorld for the entire web application which deals with stringing ClassRealms together
  • Each jar in the plugins directory would have a ClassRealm (including plugins)
  • Confluence itself would have a ClassRealm
  • The plugin's ClassRealm would be wired up to (imports) each ClassRealm it depends on (including the ClassRealm for confluence itself)
  • The plugin's thread-local class loader is then set to be the plugin's ClassRealm.getClassLoader()

This facilitates multiple versions of the same library to coexist in the plugins directory happily. With the exception in the case of a user mistake where by they include 2 versions of the same library in the same plugin twice - however this should result in an intuative error rather than an exception.

More research is needed I think - please update this page with any information you can offer.

Enabling / Disabling Plugins with Dependancies

When a plugin is enabled / installed, it's dependancies should be enabled first - and if the dependancies cannot be enabled then this plugin shouldnt be enabled either (it should show a dependancy error message (missing dependant / dependant failed to enable) in the UI as the reason for it not starting).

When a plugin is disabled / uninstalled, it's dependants should be disabled first (not uninstalled - even if this dependant is installed).

Plugins that use Bandana (XStream)

When confluence starts up / a space is accessed the Bandana configuration xml file is read and converted into a Java classes hierarchy using the XStream library. However, at this point Bandana/XStream cannot access the classes in the plugins - the classloader (or code with resolves XML > Java class) will have to be modified to hunt through plugins if it cannot find the class.

What should happen in the event that a plugin stores data in Bandana but is then uninstalled? Should the information be removed, or should it continue to error to the console as it does with no way to clean the bandana file up (other than editing the xstream xml files manually).

Labels

 
(None)
  1. Oct 18, 2007

    Keith Brophy says:

    The following page is relevant in the case where a plugin depends on a class in ...

    The following page is relevant in the case where a plugin depends on a class in another plugin:

    How to reference a plugin from my plugin

    1. Oct 18, 2007

      Dan Hardiker says:

      Hi Keith, You would need to bundle the Usage Tracking plugin inside yours so th...

      Hi Keith,

      You would need to bundle the Usage Tracking plugin inside yours so that the recursive classloader can read it. This wouldn't give you the effect you are expecting though (runtime dependency) as each plugin has it's own classloader, nor would it mean that your macro extends the plugin and has access to it's state (although that's possible, with a host of caveats).

      The simplest options for you are one of the following:

      1. Write a replacement for the Usage Tracking to do what you want
      2. Modify the Usage Tracking plugin to do what you want

      The alternatives are grim and aren't for the faint hearted.

      Dan