How to get Spring dependencies for Spring Framework?

Hello,

I’m relatively new to plugin development, and have been trying to build a Complex plugin with dependencies on another plugin. Therefore, from what I understand I need to leverage the Spring Scanner framework to inject dependencies between OSGi bundles so that I can get references to instances of runtime objects.

The problem I’m having is that for stuff such as Autowire and Component, the dependencies aren’t being pulled in at compile time and therefore when I try to execute the jira:run goal it of course fails while packaging.

I know I need to specify the package imports in the maven-jira-plugin osgi instructions, and have. However, what confuses me is how I should include the dependencies in the dependencies part of the POM.

Is there a parent POM I’m supposed to be depending on, that should pull in those dependencies? Or am I supposed to somehow find the group/artifact and the version in the runtime and pull in those (specifying them manually in …)?

I’ve been through most of the Developer documentation, through https://bitbucket.org/atlassian/atlassian-spring-scanner but am very much struggling with finding a consistent answer that gets me to at least running code for the past several days.

Any help you can offer to help me wrap my head around this would be greatly appreciated – thanks in advance!

Also, here is my example POM for reference / as a talking point:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>myplugin</artifactId>
    <version>1.0.0</version>
    <organization>
        <name>Tina</name>
        <url>https://example.com</url>
    </organization>
    <name>Example Plugin</name>
    <description>This plugin has a dependency on a marketplace plugin</description>
    <packaging>atlassian-plugin</packaging>
    <dependencies>

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

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-annotation</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <!--<scope>compile</scope>-->
            <scope>provided</scope>
        </dependency>
        <!--
        <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-runtime</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <scope>runtime</scope>
        </dependency>
        -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
            <scope>provided</scope>
        </dependency>
        <!-- WIRED TEST RUNNER DEPENDENCIES -->
        <dependency>
            <groupId>com.atlassian.plugins</groupId>
            <artifactId>atlassian-plugins-osgi-testrunner</artifactId>
            <version>${plugin.testrunner.version}</version>
            <scope>test</scope>
        </dependency>

        <!--
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        -->
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.2-atlassian-1</version>
            <scope>compile</scope>
        </dependency>
        <!-- Uncomment to use TestKit in your project. Details at https://bitbucket.org/atlassian/jira-testkit -->
        <!-- You can read more about TestKit at https://developer.atlassian.com/display/JIRADEV/Plugin+Tutorial+-+Smarter+integration+testing+with+TestKit -->
        <!--
        <dependency>
            <groupId>com.atlassian.jira.tests</groupId>
            <artifactId>jira-testkit-client</artifactId>
            <version>${testkit.version}</version>
            <scope>test</scope>
        </dependency>
        -->

        <!-- Third Party Plugin Dependencies -->
        <dependency>
            <groupId>com.somecompany</groupId>
            <artifactId>marketplaceplugin</artifactId>
            <version>${com.somecompany.marketplaceplugin.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.6</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.8.5</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-jira-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <productVersion>${jira.version}</productVersion>
                    <productDataVersion>${jira.version}</productDataVersion>
                    <jvmArgs>-Xms2g -Xmx4g -XX:MaxPermSize=4g -XX:-UseGCOverheadLimit -server</jvmArgs>
                    <applications>
                        <!-- Include Jira Software features -->
                        <application>
                            <applicationKey>jira-software</applicationKey>
                            <version>${jira.version}</version>
                        </application>
                    </applications>
                    <!-- Uncomment to install TestKit backdoor in JIRA. -->
                    <!--
                    <pluginArtifacts>
                        <pluginArtifact>
                            <groupId>com.atlassian.jira.tests</groupId>
                            <artifactId>jira-testkit-plugin</artifactId>
                            <version>${testkit.version}</version>
                        </pluginArtifact>
                    </pluginArtifacts>
                    -->
                    <enableQuickReload>true</enableQuickReload>
                    <enableFastdev>false</enableFastdev>
                    <!-- See here for an explanation of default instructions: -->
                    <!-- https://developer.atlassian.com/docs/advanced-topics/configuration-of-instructions-in-atlassian-plugins -->
                    <instructions>
                        <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
                        <!-- Add package to export here -->
                        <Export-Package>com.example.myplugin*</Export-Package>
                        <!-- Add package import here -->
                        <Import-Package>
                            org.springframework.osgi.*;resolution:="optional",
                            org.eclipse.gemini.blueprint.*;resolution:="optional",
                            com.atlassian.jira.*;resolution="optional",
                            com.atlassian.sal.api.*;resolution:="optional",
                            com.somecompany.marketplaceplugin.*;version=${com.somecompany.marketplaceplugin.version};resolution="optional",
                            org.springframework.beans.factory.annotation.*;resolution="optional",
                            org.springframework.stereotype*;resolution="optional",
                            *
                        </Import-Package>

                        <Require-Bundle>
                            com.somecompany.marketplaceplugin;bundle-version="[${com.somecompany.marketplaceplugin.version},${com.somecompany.marketplaceplugin.version}]"
                        </Require-Bundle>

                        <CONF_COMM/>
                        <!-- Ensure plugin is spring powered -->
                        <Spring-Context>*</Spring-Context>
                    </instructions>

                    <pluginDependencies>
                        <dependency>
                            <groupId>com.somecompany</groupId>
                            <artifactId>marketplaceplugin</artifactId>
                        </dependency>
                    </pluginDependencies>

                </configuration>
            </plugin>
            <plugin>
                <groupId>com.atlassian.plugin</groupId>
                <artifactId>atlassian-spring-scanner-maven-plugin</artifactId>
                <version>${atlassian.spring.scanner.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>atlassian-spring-scanner</goal>
                        </goals>
                        <phase>process-classes</phase>
                    </execution>
                </executions>
                <configuration>
                    <verbose>true</verbose>
                    <scannedDependencies>
                        <dependency>
                            <groupId>com.atlassian.plugin</groupId>
                            <artifactId>atlassian-spring-scanner-external-jar</artifactId>
                        </dependency>
                    </scannedDependencies>
                    <verbose>false</verbose>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <jira.version>7.10.2</jira.version>
        <com.atlassian.sal.api.version>3.1.0</com.atlassian.sal.api.version>
        <com.somecompany.marketplaceplugin.version>1.2.3</com.somecompany.marketplaceplugin.version>
        <amps.version>6.3.21</amps.version>
        <plugin.testrunner.version>1.2.3</plugin.testrunner.version>
        <atlassian.spring.scanner.version>2.1.8</atlassian.spring.scanner.version>
        <!-- This key is used to keep the consistency between the key in atlassian-plugin.xml and the key to generate bundle. -->
        <atlassian.plugin.key>${project.groupId}.${project.artifactId}</atlassian.plugin.key>
        <!-- TestKit version 6.x for JIRA 6.x -->
        <testkit.version>6.3.11</testkit.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
</project>

Did you ever figure this out?

The atlassian-spring-scanner uses provided scope with “${spring.version}”; however, spring.version property is not being set by my pom.xml when the SDK created the project. The scanner bitbucket page says that I can use Autowired. But, I cannot - not without additional configuration that is not divulged. And, nothing seems to tell me which version to use in my pom to get it pulled in.

Using atlas-mvn help:evaluate to evaluate ${spring.version} returns null. So, where does this version come from? Why is this so obtuse?

It’s a shame. So much confusion, so many changes over the years that searching is useless (atlassian doesn’t provide Google with a date in search results, so I frequently click on pages from 2012).

DI should never be this obtuse and difficult. Then, to add to it, no one seems to understand the difference between creating a service that is available to other plugins vs making a private service. So, that further exacerbates the searching issue: questions and answers never make a distinction between them and add to the confusion.

It’s just a mess. Why isn’t spring or spring-context included by default? Are we supposed to move away from those and use the JiraComponent/JiraImport annotations?

Why is EVERYTHING so difficult to find answers to with the Atlassian SDK?

I feel like I need to write an SDK for the SDK because it is impossible to figure out.

Oof, no response after many months…

Didn’t have my account setup to monitor replies, sorry about that!

So update is I DID figure this out. To summarize the problem:

  • We have a plugin (Configuration Management Toolkit) that we wanted to augment, for example automating pulling in versioning information from an external system and providing several pieces of automation. Using the REST API in this case wasn’t going to meet the requirements, so we had to develop a Java plugin.
  • Unfortunately, this plugin doesn’t publish its artifacts to a Maven repository. During development, the dependencies weren’t resolving during compilation (as expected, since Maven couldn’t get the jar files from anywhere).

Ultimately, our solution was this:

  1. Go to the Marketplace page and download the specific Jar in question.

  2. Import the Jar into your local repository, this way Maven can resolve the dependency:
    cd path/to/downloads/folder
    atlas-mvn install:install-file -DgroupId=com.deniz.jira -DartifactId=versioning -Dversion=1.13.8-jira7 -Dpackaging=jar -Dfile=versioning-1.13.8-jira7-obf.jar

  3. Add the dependency to your POM as usual (provided scope, since the runtime provides it). Since you are pulling in something locally, make sure it matches what you declared in the install:install-file Maven goal above (DgroupId, DartifactId, and Dversion)…This is a Maven thing.

    <dependency>
        <groupId>com.deniz.jira</groupId>
        <artifactId>versioning</artifactId>
        <version>1.13.8-jira7</version>
        <scope>provided</scope>
    </dependency>
  1. Declare your OSGI imports; we want to use the service provided by the third party plugin, which means we have to request importing the runtime dependency from another OSGI container / app context…This is an OSGi thing.
    You can also include the pluginArtifact so that the dependent version is installed with yours and pluginDependencies so Jira knows your plugin depends on the other plugin. (otherwise your plugin will fail to start…so you’ll have to manually install the 3rd party plugin and then enable your plugin from the UPM…ick. And tbh pluginDependencies doesn’t have to be there but if you exclude it then you have to )
<build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>jira-maven-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                   ...
                    <instructions>
                        <Atlassian-Plugin-Key>${project.groupId}.${project.artifactId}</Atlassian-Plugin-Key>
                        <Spring-Context>*</Spring-Context>
                        <Export-Package>
                            ...
                        </Export-Package>
                        <Import-Package>
                            com.deniz.jira.versioning,
                            com.deniz.jira.versioning.bundles,
                            com.deniz.jira.versioning.versiongraph,
                           ...
                        </Import-Package>
                    </instructions>

                    <pluginArtifacts>
                        <pluginArtifact>
                            <groupId>com.deniz.jira</groupId>
                            <artifactId>versioning</artifactId>
                            <version>${com.deniz.jira.versioning.version}</version>
                        </pluginArtifact>
                    </pluginArtifacts>

                    <pluginDependencies>
                        <dependency>
                            <groupId>com.deniz.jira</groupId>
                            <artifactId>versioning</artifactId>
                        </dependency>
                    </pluginDependencies>

                </configuration>
            </plugin>
            ...
    </plugins>
</build>

And that’s pretty much it. There were some other details related to dependency resolution in this particular case that I don’t want to go into here.

TL;DR: This was a case of getting remote Jars into my local repository so Maven could resolve them during compilation. Not related to Jira at all…

…THAT said, the docs could be lots clearer about what is involved in plugin development…If you were new to this kind of stuff like I was, it’s not always clear what is Atlassian’s thing and what isn’t and they rely on you filling in the gaps in a looooot of places (OSGi, Spring, Atlassian’s Maven plugin that manages OSGi/Spring stuff…It’s confusing and takes a while to sort out/learn).

Worth nothing, for example, I developed this in 7.10.2…But you don’t use the <component-import> directive in the atlassian-plugin xml anymore…You use the @ComponentImport annotation on classes directly (Atlassian’s thing) to inject runtime instances into your plugin’s container from some other container in the OSGi environment. BUT if you are using your own local services then they’re going to be local to your container and you will just want to use regular Spring annotations to wire dependencies (@Component to declare as a bean and @Autowired to inject that runtime instance into some other class, for example)…Ech.

Docs are a mix of both, it takes experience to sort that out unfortunately and the wall is steep so gooood luck. :confused:

1 Like

Hey, an update to this:

So I’ve now learned it’s better to use a configured (local) repository rather than using whatever the user happens to have cached in their maven local repo (~/.m2 on Mac), for several reasons:

  • Easier to use in build / CD stuff (like a Maven box in GH actions)
  • Easy to swap out for a remote repo eventually
  • Can be version-controlled (could be a con, but fine in this case since it’s just one artifact…)

All I had to do was:

  1. Create a folder in my project to act as the repo (included in source-control)
  2. Update my POM with the repository
  3. Install into the local repo

In my case, my project directory is:

my-project
    |-- local-lib
    |-- src
    |-- pom.xml

And I added this to my repositories:

<repositories>

    <repository>
         <id>my-local-lib</id>
         <url>file://${project.basedir}/local-lib</url>
     </repository>

</repositories>

And then could install it locally by copying the jar to my directory, installing the jar, and then deleting it:

mv ~/Downloads/versioning-1.15.3-obf.jar .      # Move the jar to the project directory
atlas-mvn org.apache.maven.plugins:maven-install-plugin:2.3.1:install-file \
-DgroupId=com.deniz.jira \
-DartifactId=versioning \
-Dversion=1.15.3 \
-Dpackaging=jar \
-Dfile=versioning-1.15.3-obf.jar \
-DlocalRepositoryPath=lib
rm versioning-1.15.3-obf.jar                    # Remove the jar from the project directory

After installation, the directory looks like this:

my-project
    |-- local-lib
        |-- com
            |-- deniz
                |-- jira
                    |-- versioning
                        |-- 1.15.3
                            |-- versioning-1.15.3.jar
                            |-- versioning-1.15.3.pom
                        |-- maven-metadata-local.xml
    |-- src
    |-- pom.xml
2 Likes