Contents
Table of Contents |
---|
Prerequisites
Ensure that you are good on the following prerequisites:
A decent grasp of the Java language
Basic knowledge about the toolchain (maven specifically)
Certain Spring framework notions (injection)
Know Atlassian app basics (see https://developer.atlassian.com/ for details). You will also need Atlassian SDK (see this documentation for reference)
An IDE (e.g. Eclipse). We use JetBrains IntelliJ Idea here.
Setup
Install the Atlassian developer kit from the terminal/command line
Code Block theme Emacs brew tap atlassian/tap brew install atlassian/tap/atlassian-plugin-sdk
Download your desired version of the SIL Engine from the marketplace.
Add the SIL Engine to the local maven repository
Template
Code Block language powershell themeEmacs title Template atlas-mvn install:install-file -Dfile=<Location of Jar from step 2> -DgroupId=com.keplerrominfo.jira.plugins -DartifactId=katl-commons -Dversion=<Version of Jar> -Dpackaging=jar
Example
Code Block language powershell theme Emacs title Example atlas-mvn install:install-file -Dfile=/var/atlassian/katl-commons-4.1.6-r20180917150405.jar -DgroupId=com.keplerrominfo.jira.plugins -DartifactId=katl-commons -Dversion=4.1.6 -Dpackaging=jar
...
Upon finishing your IDE wizard, you will notice that there's a pom.xml in the root of the project. Open it and replace its content with the following:
...
pom.xml
Code Block | |||
---|---|---|---|
| |||
<?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.mycompany</groupId> <artifactId>silexample</artifactId> <version>4.0.0</version> <name>silexample</name> <description>Example: integrating SIL routines and descriptors</description> <packaging>atlassian-plugin</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <katlcommons.version>4.0.0-SNAPSHOT</katlcommons.version> <spring.osgi.version>1.2.1</spring.osgi.version> <spring.version>4.1.6.RELEASE</spring.version> <jira.version>7.0.0</jira.version> <jira.data.version>7.0.0</jira.data.version> <jira.sal.version>3.0.0</jira.sal.version> <jira.event.version>2.3.5</jira.event.version> <atlassian.spring.scanner.version>1.2.13</atlassian.spring.scanner.version> <silengine.version>4.1.4</silengine.version> <plugin.testrunner.version>2.0.0</plugin.testrunner.version> <amps.version>6.1.2</amps.version> </properties> <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>${jira.sal.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.atlassian.event</groupId> <artifactId>atlassian-event</artifactId> <version>${jira.event.version}</version> <scope>provided</scope> </dependency> <!-- This is for the annotations you need to indicate components and OSGI services. --> <dependency> <groupId>com.atlassian.plugin</groupId> <artifactId>atlassian-spring-scanner-annotation</artifactId> <version>${atlassian.spring.scanner.version}</version> <scope>compile</scope> </dependency> <!-- This is the runtime code for interpreting the build time index files --> <dependency> <groupId>com.atlassian.plugin</groupId> <artifactId>atlassian-spring-scanner-runtime</artifactId> <version>${atlassian.spring.scanner.version}</version> <scope>runtime</scope> </dependency> <!-- ===================================== --> <!-- cPrimeAppfire dependencies --> <!-- ===================================== --> <dependency> <groupId>com.keplerrominfo.jira.plugins</groupId> <artifactId>katl-commons</artifactId> <scope>provided</scope> <version>${silengine.version}</version> </dependency> <!-- ===================================== --> <!-- Spring & common dependencies --> <!-- ===================================== --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <scope>provided</scope> <version>${spring.version}</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <scope>provided</scope> </dependency> <!-- ===================================== --> <!-- TEST dependencies --> <!-- ===================================== --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-tests</artifactId> <version>${jira.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.plugins</groupId> <artifactId>atlassian-plugins-osgi-testrunner</artifactId> <version>${plugin.testrunner.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-func-tests</artifactId> <version>${jira.version}</version> <scope>test</scope> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <excludes> <exclude>atlassian-plugin.xml</exclude> <exclude>META-INF/KINFO</exclude> <exclude>META-INF/KVERSION</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>atlassian-plugin.xml</include> <include>META-INF/KINFO</include> <include>META-INF/KVERSION</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>com.atlassian.maven.plugins</groupId> <artifactId>maven-jira-plugin</artifactId> <version>${amps.version}</version> <extensions>true</extensions> <configuration> <compressResources>false</compressResources> <useFastdevCli>false</useFastdevCli> <enableFastdev>false</enableFastdev> <enableDevToolbox>false</enableDevToolbox> <enablePde>false</enablePde> <enableQuickReload>true</enableQuickReload> <quickReloadVersion>1.29</quickReloadVersion> <productVersion>${jira.version}</productVersion> <productDataVersion>${jira.version}</productDataVersion> <!-- Enable here the products if you want container tests (you should) --> <products> <product> <id>jira</id> <instanceId>jira-${jira.version}</instanceId> <version>${jira.version}</version> <dataVersion>${jira.data.version}</dataVersion> <!-- dataPath>${basedir}/src/test/resources/generated-test-resources.zip</dataPath --> </product> </products> <pluginArtifacts> <!-- installed in the product when using atlas-run or atlas-debug --> <pluginArtifact> <groupId>com.keplerrominfo.jira.plugins</groupId> <artifactId>katl-commons</artifactId> <version>${silengine.version}</version> </pluginArtifact> </pluginArtifacts> <pluginDependencies> <!-- OBR dependencies --> <pluginDependency> <groupId>com.keplerrominfo.jira.plugins</groupId> <artifactId>katl-commons</artifactId> </pluginDependency> </pluginDependencies> <testGroups> <testGroup> <id>wired-jira-integration</id> <productIds> <productId>jira-test</productId> </productIds> <includes> <include>it/**/*WiredTest.java</include> </includes> </testGroup> </testGroups> <testBundleExcludes> <testBundleExclude> <groupId>com.keplerrominfo.jira.plugins</groupId> <artifactId>katl-commons</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>sil</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>stl</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>sil-remote-client</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>sil-sms-client</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>kepler-util</artifactId> </testBundleExclude> <testBundleExclude> <groupId>com.keplerrominfo</groupId> <artifactId>kepler-refapp</artifactId> </testBundleExclude> <testBundleExclude> <groupId>xalan</groupId> <artifactId>xalan</artifactId> </testBundleExclude> <testBundleExclude> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </testBundleExclude> </testBundleExcludes> <instructions> <Bundle-Name>SIL Example</Bundle-Name> <Bundle-SymbolicName>com.mycompany.silexample</Bundle-SymbolicName> <Atlassian-Plugin-Key>com.mycompany.silexample</Atlassian-Plugin-Key> <Spring-Context>*</Spring-Context> <Export-Package> !net.java.ao, !com.atlassian, com.mycompany.silexample.* </Export-Package> <Import-Package> com.keplerrominfo.refapp.launcher;resolution:="optional", com.keplerrominfo.refapp.launcher.*;resolution:="optional", org.springframework.osgi.*;resolution:="optional", org.eclipse.gemini.blueprint.*;resolution:="optional", com.atlassian.plugin.osgi.bridge.external, *;resolution:="optional" </Import-Package> </instructions> <skipManifestValidation>true</skipManifestValidation> <systemPropertyVariables> <webdriver.browser>htmlunit</webdriver.browser> <!-- You can also use chrome if you have it installed. You'll also need a webDriver for Chrome, which you can get from https://sites.google.com/a/chromium.org/chromedriver/downloads and place it somewhere on the disk. Then just comment the htmlunit driver above and uncomment below. --> <!--<webdriver.browser>chrome</webdriver.browser>--> <!--<webdriver.chrome.driver>D:/tools/chromedriver.exe</webdriver.chrome.driver>--> </systemPropertyVariables> </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> <!-- process-classes seems to be skipped if you are using scala so perhaps use prepare-package --> <phase>process-classes</phase> </execution> </executions> <configuration> <includeExclude>+com.mycompany.*</includeExclude> <verbose>true</verbose> <scannedDependencies> <!-- - Add here the scanned dependencies, if any <dependency> <groupId>com.somegroup</groupId> <artifactId>artifact.id</artifactId> </dependency> --> </scannedDependencies> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>LATEST</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.4.3</version> <configuration> <argLine>-Xms128m -Xmx256m</argLine> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>LATEST</version> </plugin> </plugins> </reporting> </project> |
Note | |
---|---|
title | SIL VersionThe property <sil.version> will need to be updated to the version installed in the Setup section |
You will notice that everything revolves around two maven plugins:
maven-jira-plugin – the building plugin
atlassian-spring-scanner-maven-plugin – a helper plugin, responsible to scan your sources and create spring beans from annotations.
You can find more information about these plugins in the Atlassian documentation.
...
In the resources directory, create a META-INF/spring directory and add a spring.xml file.
...
...
spring.xml
Code Block | |||
---|---|---|---|
| |||
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:atlassian-scanner="http://www.atlassian.com/schema/atlassian-scanner" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.atlassian.com/schema/atlassian-scanner http://www.atlassian.com/schema/atlassian-scanner/atlassian-scanner.xsd"> <atlassian-scanner:scan-indexes /> <!-- - Please un-comment this to enable active object support in your plugin - <bean class="com.atlassian.activeobjects.external.TransactionalAnnotationProcessor"> <constructor-arg ref="activeObjects"/> </bean> --> </beans> |
...
We will create two more files in the META-INF directory: KINFO and KVERSION . These files are required by the cPrime Appfire framework support and while KINFO is free text (you can put there anything you want), KVERSION has a fixed structure, it must contain the released date of the plugin:
...
META-INF/KVERSION
Code Block |
---|
RELEASED: ${release.date} |
Code Block | title |
META-INF/KINFO
Code Block |
---|
JIRA SIL Module: silexample, version ${version} Simple SIL extension example. Copyright (c) cPrimeAppfire, Inc. Visit us at https://www.cprimeanovaapps.com/ |
Another thing to work on is the atlassian-plugin.xml file or the add-on descriptor. Create an empty add-on descriptor, like this:
...
...
atlassian-plugin.xml
Code Block | ||
---|---|---|
| ||
<atlassian-plugin key="com.mycompany.silexample" name="SIL Example" plugins-version="2"> <plugin-info> <description>SIL Basic Example</description> <version>${version}</version> <vendor name="cPrimeAppfire, Inc." url="https://www.cprimeanovaapps.com/" /> <param name="plugin-icon">images/generic.png</param> <param name="plugin-logo">images/generic.png</param> <param name="vendor-icon">images/keplerLogo.png</param> <param name="vendor-logo">images/keplerLogo.png</param> <param name="atlassian-data-center-compatible">true</param> </plugin-info> </atlassian-plugin> |
...
In the end, the structure will look like this:
...
Now you are ready to code.
...
Open the editor, and create a new java class named ExamplePluginInfoService in the com.mycompany.silexample package.
...
...
ExamplePluginInfoService.java
Code Block | |||
---|---|---|---|
| |||
/* * File: ExamplePluginInfoService.java * Created by rdumitriu on 13.12.2016. */ package com.mycompany.silexample; import javax.annotation.Nonnull; import com.keplerrominfo.refapp.config.PluginInfoService; import org.springframework.stereotype.Component; /** * The plugin info service * * @author Radu Dumitriu (rdumitriu@gmail.com) * @version 1.0 * @since 1.0 */ @Component public class ExamplePluginInfoService implements PluginInfoService { @Nonnull @Override public String getKey() { return "com.mycompany.silexample"; } @Nonnull @Override public String getName() { return "SIL Example"; } } |
...
Now, register certain routines and CF descriptors, and you can do that at launch time of the plugin. The start-stop sequence of plugins in complicated enough therefore we use our own Launcher class, abstracting all those events in such a way that you will know that whatever you do, it is safe to be done. So, extend / implement com.keplerrominfo.refapp.launcher.AbstractPluginLauncher: create an ExampleLauncher class within the same package (com.mycompany.silexample). Override doAtLaunch() and doAtStop() members.
...
...
ExampleLauncher.java
Code Block | |||
---|---|---|---|
| |||
/* * File: ExampleLauncher.java * Created by rdumitriu on 12.12.2016. */ package com.mycompany.silexample; import com.atlassian.event.api.EventPublisher; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.project.ProjectManager; import com.atlassian.plugin.spring.scanner.annotation.component.ClasspathComponent; import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.sal.api.lifecycle.LifecycleAware; import com.keplerrominfo.jira.commons.ivm.*; import com.keplerrominfo.refapp.config.*; import com.keplerrominfo.refapp.config.impl.PluginConfigurationServiceImpl; import com.keplerrominfo.refapp.launcher.AbstractPluginLauncher; import com.keplerrominfo.sil.lang.RoutineRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * This is a standard launcher for a cPrimeAppfire add-on. We respect here the life-cycle of the add-on within the whole JIRA, * so make sure you follow it. * * @author Radu Dumitriu (rdumitriu@gmail.com) * @version 1.0 * @since 1.0 */ @Component @ExportAsService(LifecycleAware.class) public class ExampleLauncher extends AbstractPluginLauncher { private static final Log LOG = LogFactory.getLog(ExampleLauncher.class); private final CustomFieldDescriptorRegistry customFieldDescriptorRegistry; private final ProjectManager projectManager; @Autowired public ExampleLauncher(@ComponentImport EventPublisher eventPublisher, //this is the local component providing info about the add-on (ExamplePluginInfoService) PluginInfoService pluginInfoService, //do not remove this, it will generate the correct component-import @ComponentImport CommonPluginConfigurationService commonPluginConfigurationService, @ComponentImport HostConfigurationProvider hostConfigurationProvider, @ClasspathComponent PluginConfigurationServiceImpl pluginConfigurationService, @ComponentImport CustomFieldDescriptorRegistry customFieldDescriptorRegistry, @ComponentImport ProjectManager projectManager) { super(eventPublisher, pluginInfoService, hostConfigurationProvider, pluginConfigurationService); this.customFieldDescriptorRegistry = customFieldDescriptorRegistry; this.projectManager = projectManager; } @Override public void doAtLaunch() { super.doAtLaunch(); //HERE:: will do more at launch } @Override public void doAtStop() { //HERE:: will do cleanup super.doAtStop(); } } |
...