This is a lengthy and quite technical blog post about obstacles overcome while developing a custom plugin for Atlassian Jira 7.12.3.
You may have come to this page seeking a solution to one of the following error messages:
Unexpected exception parsing XML document from URL [bundle://....0:0/META-INF/spring/atlassian-plugins-component-imports.xml]; nested exception is org.springframework.beans.FatalBeanException: Class [org.eclipse.gemini.blueprint.config.OsgiNamespaceHandler] for namespace [http://www.eclipse.org/gemini/blueprint/schema/blueprint] does not implement the [org.springframework.beans.factory.xml.NamespaceHandler] interface
Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.eclipse.org/gemini/blueprint/schema/blueprint] Offending resource: URL [bundle://....0:0/META-INF/spring/atlassian-plugins-component-imports.xml]
Cannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=COM.jrockit.reflect)
Cannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=com.atlassian.tutorial.myPlugin.api) [caused by: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=org.springframework.osgi.service.exporter.support)]
'...-tests' - '...' failed to load. The plugin has been disabled. A likely cause is that it timed out during initialisation It has the following missing service dependencies : ...
lang.Exception: No tests found in class [it....]
No tests found
The article below records the steps I took to overcome the above issues. I don’t claim that it is the only way to solve them, just that it worked for me. Unfortunately, if you are new to Atlassian plugin development the learning curve can be very steep and the documentation can be hard to find. I hope I can save you a few hours or days of searching for the correct information. References to some relevant Atlassian documentation can be found at the end.
My sincere thanks to several Zuhlke colleagues who worked on the solution with me: Mirko Sciachero, Giovanni Asproni, Michael Dickens and Michael Scott.
A little architectural background
When I started this work, I knew nothing about the way the Jira server is built, but I soon discovered that I needed to be better informed if I was to have any chance of working out what was going wrong. The following diagram is a very simple-minded idea of the way in which dependencies are fulfilled within the web application.
The plugin we wish to develop runs within the context of a multi-threaded web app (Jira). Integration/wired tests are run from a complementary plugin installed within the same environment.
Both plugins have dependencies on multiple Java libraries. A Java application satisfies its dependencies at runtime by calling its class loader to obtain them. Standard class loaders delegate to their parents first, and only start searching the locations on their class path if the parent is unable to load the required resource or class.
However, in the case of Jira it’s different. Jira wants to avoid every plugin loading copies of the same class, as this could easily increase the amount of memory used for class storage by a few orders of magnitude. It uses the OSGi framework, which comes with Spring, to provide a cache of already-loaded classes (called Felix) and to satisfy all dependencies from this cache.
This has important implications for the plugin configuration. Felix only loads classes permitted by the plugin configuration. This configuration is composed of instructions in the atlassian-plugin.xml file (a resource), the project POM, and some Atlassian defaults computed at build time.
The result is that it is very easy to encounter NoClassDefFound exceptions even though the required class is on the class path. Whenever this happens, it is worth checking the plugin configuration for missing package-import statements.