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:
Tag | Default Value | Purpose |
---|---|---|
Atlassian-Plugin-Key | Maven group and artifact | Marks 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-Package | Your API package | Tells 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 The final asterisk |
Spring-Context | Catch-all wildcard | This 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. |
*
Import-Package wildcardPutting 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 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:
import com.foo.Bar;
(the most common case), orcom.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:
atlassian-plugin.xml
),Class.forName("com.foo.Bar")
(because these are not static references)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.
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.
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:
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.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).
Rate this page: