How to add license API support to a JIRA plugin?

I’m trying to get a JIRA plugin on the marketplace and it needs to use the licensing API. I’m trying to do it as described in the documentation, but keep running into problems. :worried:

Reducing the issue to the minimum required for reproducing, start by making a new plugin - run these three commands one after another - atlas-create-jira-plugin, then atlas-create-jira-plugin-module (add 11 for “Licensing API support”), then “atlas-package”. This fails with

[ERROR] atlassian-plugin.xml contains a definition of component-import. This is not allowed when Atlassian-Plugin-Key is set.

Apparently create-jira-plugin creates a plugin with the Spring Scanner enabled (version 1.2.13 according to pom.xml), while atlas-create-jira-plugin-module creates it in the “old style” by adding <component> and <component-import> to the atlassian-plugin.xml.

Oh, well. I can try to work around that. I removed all the offending tags from atlassian-plugin.xml and then added @Component and @ComponentImport annotations to the .java files. This builds just fine, but when I start atlas-run I now get in the console:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘com.my.plugin.license.LicenseHelloWorldServlet’: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManager]: : No qualifying bean of type [com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport(value=)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport(value=)}

And a bunch more in the same vein. Basically, the dependency injection fails.

I’ll add to this that I’ve never been able to get the dependency injection to work for my plugin at all, even for basic stuff. In the end I was able to work around that by using static methods on ComponentAccessor, although I know it’s not the recommended way. However I cannot obtain the licensing classes in this fashion. Neither ComponentAccessor nor ComponentManager can give them to me; I only get null references no matter which method I use.

However the local JIRA instance shows that “Atlassian Universal Plugin Manager Plugin” is installed and on version 2.20.7.

I’m at my wits end here. What am I missing? It must be something simple, but what?

Is the problem similar to the one described at the link below?

Just checked my own add-on. It doesn’t depend on ThirdPartyPluginLicenseStorageManager, but on PluginLicenseManager.

    <component-import key="pluginLicenseManager"
                      interface="com.atlassian.upm.api.license.PluginLicenseManager" />

Still no luck. I changed the SAL version to 3.0.7 (gleaned from the jira installation in the “target” folder) and changed to PluginLicenseManager, but still the same:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘com.my.plugin.LicenseHelloWorldServlet’: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.atlassian.upm.api.license.PluginLicenseManager]: : No qualifying bean of type [com.atlassian.upm.api.license.PluginLicenseManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport(value=)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.atlassian.upm.api.license.PluginLicenseManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport(value=)}

Also I’m using that Spring Scanner thing which replaces <component-import> with @ComponentImport.

I also tried changing the UPM version but then it didn’t build (could not download the required UPM version, even though it was in the JIRA installation).

Here’s a snipped of my code:

import org.springframework.stereotype.Component;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class LicenseHelloWorldServlet extends HttpServlet
{
    private final PluginLicenseManager licenseManager;

    @Autowired
    public LicenseHelloWorldServlet(@ComponentImport PluginLicenseManager licenseManager) {
        this.licenseManager = licenseManager;
    }

Do you have dependencies in your POM like this?

        <dependency>
            <groupId>com.atlassian.upm</groupId>
            <artifactId>licensing-api</artifactId>
            <version>${upm.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.upm</groupId>
            <artifactId>upm-api</artifactId>
            <version>${upm.version}</version>
            <scope>provided</scope>
        </dependency>

Yes, a whole bunch of it. The create-jira-plugin-module added this:

    <dependency>
        <groupId>com.atlassian.upm</groupId>
        <artifactId>plugin-license-storage-lib</artifactId>
        <version>${upm.license.compatibility.version}</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.atlassian.upm</groupId>
        <artifactId>plugin-license-storage-plugin</artifactId>
        <version>${upm.license.compatibility.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.atlassian.upm</groupId>
        <artifactId>licensing-api</artifactId>
        <version>${upm.license.compatibility.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.atlassian.upm</groupId>
        <artifactId>upm-api</artifactId>
        <version>${upm.license.compatibility.version}</version>
        <scope>provided</scope>
    </dependency>

Where the ${upm.license.compatibility.version} was set to 2.4.1 initially. I tried changing that to 2.20.7 but that broke the build. I also tried commenting that out and adding this instead:

<dependency>
    <groupId>com.atlassian.upm</groupId>
    <artifactId>licensing-api</artifactId>
    <version>2.21.4</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.upm</groupId>
    <artifactId>upm-api</artifactId>
    <version>2.21</version>
    <scope>provided</scope>
</dependency>

But that too broke the build. On top of that, I also have:

                    <Private-Package>com.atlassian.upm.license.storage.lib*</Private-Package>
                    <DynamicImport-Package>com.atlassian.upm.api.license.entity;version="2.0.1", com.atlassian.upm.api.license;version="2.0.1", com.atlassian.upm.api.util;version="2.0.1", com.atlassian.upm.license.storage.plugin;version="${upm.license.compatibility.version}"</DynamicImport-Package>
                </instructions>
                <bundledArtifacts>
                    <bundledArtifact>
                        <groupId>com.atlassian.upm</groupId>
                        <artifactId>plugin-license-storage-plugin</artifactId>
                        <version>${upm.license.compatibility.version}</version>
                    </bundledArtifact>
                </bundledArtifacts>

Hmm, I just tried this tutorial and at last it worked! So now I have something to compare my actual plugin to. Will keep you posted on which detail it was that was missing.

Yes, please do. I would be interested to learn what the problem was.

Finally, I found it. A very obscure thing too - only paying close attention to the myriad of exceptions normally found in the atlas-run console was I able to figure out the clue that I was missing.

It seems that I was lying in the original post. That specific scenario will probably work. Probably, because I haven’t actually tested it, but it was almost what I had done, so I assumed…

Anyway, there are two parts here.

Part 1 - as I was cleaning up my plugin from the default hello-world sample files, somewhere along the line I overzealously deleted the folder resources/META-INF which contained a file with a crucial instruction that spring scanner was used in this plugin. Later, when JIRA picked up the compiled JAR file, it didn’t find this file and initialized it in some different fashion without all the spring and dependency injection stuff.

Sorry, this is my first Java project, so I’m half-guessing here. :slight_smile:

Adding back this folder brought the plugin back to the warm embrace of osgi/spring, but that then choked too because -

Part 2. Besides the licensing servlet my plugin also has a report class which does all the useful work. And for that class I had a constructor like this:

@Autowired
public MyReport()
{
}

Seems harmless, a leftover after my previous futile efforts in getting dependency injection to work. Except that the osgi/spring/whatever doesn’t like an @Autowired constructor without an actually injected parameter. Doing that produces an exception when it tries to load the plugin and set up some sort of “context” for it. The effort is then abandoned and the plugin discarded entirely.

Removing this annotation I now have a working plugin and working dependency injection. Yay! :tada:

1 Like