Last updated Mar 27, 2024

Configuration of Instructions in Atlassian Plugins

Default plugin instructions

If you create a new plugin with the Atlassian SDK (for instance using atlas-create-confluence-plugin), you'll find the following instructions already configured for you:

1
2
<instructions>
  <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
  <!-- Add package to export here -->
  <Export-Package>
    com.mycompany.api,
  </Export-Package>
  <!-- Add package import here -->
  <Import-Package>
    org.springframework.osgi.*;resolution:="optional",
    org.eclipse.gemini.blueprint.*;resolution:="optional",
    *
  </Import-Package>
  <!-- Ensure plugin is spring powered -->
  <Spring-Context>*</Spring-Context>
</instructions>

Here's what these default instructions do:

TagDefault ValuePurpose
Atlassian-Plugin-KeyMaven group and artifactMarks your plugin as providing its own Spring configuration, rather than one being created during the transformation process. This is so-called 'transformerless', and results in significantly faster deployment and startup time of your plugin.
Export-PackageYour API packageTells the underlying OSGi framework to expose these classes outside your plugin. Without it, you would get ClassNotFoundExceptions when other plugins attempted to load your API classes.
Import-Package

Optional backend frameworks

Catch-all wildcard

In order to provide Spring functionality within an OSGi container, we use a third party framework. Originally this was Spring DM, but in the latest Atlassian Plugins version we migrated to its successor, Gemini Blueprints. Marking both the Spring and Blueprint packages as optional means your plugin can find the classes it needs in both new and old Atlassian products. If you removed these two optional imports, bnd would instead generate mandatory imports for both of these OSGi frameworks, meaning your plugin would fail to resolve or start in both newer and older products.

The final asterisk * is a wildcard that attempts to ensure your plugin can access all the classes it needs; see below for a full explanation.

Spring-ContextCatch-all wildcardThis is simply a safety measure to ensure your Spring context is loaded, even if there are no XML files in your META-INF/spring directory.

The asterisk * Import-Package wildcard

Motivation

Putting an asterisk * as the last entry of your <Import-Package> instruction is a convenient way of reducing the number of packages that you have to import explicitly. Without this wildcard, you would have to figure out (and maintain) a complete list of:

  • the external (non-bundled) packages used by your plugin's own code,
  • the external packages used by any dependencies your plugin bundles (extracted or not), and
  • the packages exported by your plugin (because best practice says to import these as well).

How it works

The asterisk is resolved at build time by a utility called bnd, which AMPS invokes via the maven-bundle-plugin. If you look at the manifest it generates (META-INF/MANIFEST.MF, located in both target/classes and the plugin JAR), you will note that BND has expanded the asterisk into a list of the packages that it thinks your plugin needs to import from OSGi.

BND will generate an Import-Package entry for a package if:

  • you haven't already imported it (or excluded it) explicitly,
  • it's not found within your plugin JAR (which includes any dependencies bundled into it), and
  • any class file in your plugin JAR (again, including bundled dependencies) contains a static reference to a type from that package, in the form of either:
    • an explicit Java import statement such as import com.foo.Bar; (the most common case), or
    • a fully-qualified inline reference (e.g. com.foo.Bar bar = new com.foo.Bar();)

BND will not generate an Import-Package entry for packages whose only usage by your plugin is in:

Therefore, if your plugin requires a package referenced only by one or more of the above methods, you need to explicitly add that package to your AMPS <Import-Package> instruction, because BND has no idea that you require it.

Another situation in which you need to explicitly declare an imported package, even if its usage is visible to BND, is when it's not available in all deployment scenarios. For example, a cross-product plugin that uses packages from two different products (e.g. Confluence and Jira) would need to declare both of those package imports as optional, because there's no scenario in which both the Confluence and Jira packages will be available from OSGi, meaning the plugin could never be enabled. Another example would be when a package only exists in a specific version of the host product. In both of the above cases, you would mark the import as optional and ensure that your Java code gracefully handled the absence of the classes in question.

Dependency versioning

If a dependency's manifest contains a Bundle-Version header, BND will add a version range to any imports that it generates for packages in that artifact. For example, if dependency has a Bundle-Version header that declares version 1.2.3, BND will generate a package import with a version range of [1.2,2), meaning version 1.2 or higher, but not version 2.x or later. If you're not happy with this auto-generated range, simply add an explicit import of that package to your <Import-Package> instruction, with or without your own version range, and BND will not generate one.

Making the wildcard optional

Some sources recommend marking the wildcard import as optional, i.e. *;resolution:="optional". Ultimately this is an engineering decision that you as a developer have to make:

  • Making the asterisk optional: all the imports that BND generates for you will be marked optional, which means that even if some or all of the packages you require from OSGi are missing, your plugin will still be enabled. However, whether it functions correctly is an entirely different thing; as soon as it tries to load a class from a missing package, it will fail with a ClassNotFoundException or a NoClassDefFoundError. The problem is that this failure could occur at any time after your plugin loads, even days or weeks later. The upside is that your plugin might be able to work without the classes in question, depending upon what code paths are actually exercised at runtime.
  • Leaving the asterisk as mandatory: all the imports that BND generates for you will implicitly be mandatory. The upside of this approach is that the plugin will fail fast with an informative error if any of these packages are not available when the plugin tries to load. The downside is that BND might generate mandatory imports for packages that you never actually need at runtime. For example, a StringUtils class in a bundled dependency might have a method that delegates to a hashing class from the com.example.hashing package in one of its own dependencies. Even if you never call that method, i.e. your plugin works fine without that package, BND will detect the reference to it and generate a mandatory import for it. This in turn means that unless the host OSGi container happens to provide that package, your plugin will fail to load, requiring you to add an explicit exclusion of that package to your Import-Package instruction (i.e. !com.example.hashing.*). You might need to add several such exclusions, one by one, until your plugin eventually loads. Even then, you can't be certain that all of your mandatory package imports are legitimate, because any of them could be for a package that just happens to be present in your test environment but is never present in production. Furthermore, when confronted with an obscure package name from a transitive dependency, it can be hard to know whether it can safely be excluded or whether its absence is a legitimate bug.

Because neither of these two approaches is completely satisfactory, it's really important to have a comprehensive suite of user acceptance tests for your plugin's features, and to run them in an appropriate subset of the intended production environments (for example in several versions of each supported product).

References

Rate this page: