Understanding the Generated License API Support

The LicenseHelloWorldServlet Class

The LicenseHelloWorldServlet class contains example of simple plugin functionality. The servlet displays a different message depending on whether the plugin has a license or not. It is best practice to execute a license validity check at every plugin entry point (e.g. servlet, REST resource, and webwork action). This ensures that users cannot use your plugin without a valid license.

Generating this example servlet is optional.

The LicenseServlet Class

The generator creates the LicenseServlet class. The LicenseServlet class acts as a license administration page. This page is necessary to support input of a license key in host applications where UPM 2.0 is not installed. The servlet includes the following key methods:

doGet()

Renders the page or redirects to the login if the user is not logged in.

doPost()

Handles POST operations on the page.

addEligibleMarketplaceButtons()

A private method that determines which Marketplace buttons are possible within the page's current state.

initVelocityContext()

A private method that builds the page.

hasAdminPermission()

A private method that determines if the current user is authorized to enter a license.

The license administration screen displays buttons for each Marketplace action (Buy, Try, Renew, and Upgrade) that the plugin is eligible for. For example, if the plugin is unlicensed, Buy and Try buttons display. If the plugin has a maintenance-expired license, a Renew button displays.
Clicking any of these buttons fires a POST request to My Atlassian.

Following the acquisition of the new license, My Atlassian installs the license into the user's Atlassian product and redirects back to the source (in this example, the license administration page).

When a licensing-aware UPM is detected your plugin no longer uses the servlet to update the license. Attempts to update through the servlet are not allowed. If a licensing-aware UPM is present, it disables the mechanism to update your plugin license. Instead, it displays a message to the user that updating their license should occur in UPM and provide a URI to redirect to.

The generator adds two files to src/main/resources — the /license-admin.vm file and the atlassian-plugin.properties file. These files support internationalization of the administration page defined by LicenseServlet.

Changes to the pom.xml file

Both servlets have dependencies on the Plugin License API. This section details the dependency changes made by the generator.

Import Licensing Packages

The generator ensures that Maven imports the appropriate packages by adding the following <dependency> entries to <dependencies> element:

<dependency>
    <groupId>com.atlassian.upm</groupId>
    <artifactId>plugin-license-storage-lib</artifactId>
    <version>${upm.license.compatibility.version}</version>
    <scope>compile</scope> <!-- intentionally compile scoped -->
</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>
<dependency>
    <groupId>com.atlassian.sal</groupId>
    <artifactId>sal-api</artifactId>
    <version>2.4.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework.osgi</groupId>
    <artifactId>spring-osgi-core</artifactId>
    <version>1.1.3</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.4</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.templaterenderer</groupId>
    <artifactId>atlassian-template-renderer-api</artifactId>
    <version>1.0.5</version>
    <scope>provided</scope>
</dependency>

Instructions for the maven-jira-plugin

The generator also edits the maven-jira-plugin artifact's configuration in the <plugins> section and adds some instructions. These instructions allow your plugin to dynamically detect the Licensing API when it is available.

These instructions are in a <plugin> element within <plugins> and look like the following:

<plugin>
    <groupId>com.atlassian.maven.plugins</groupId>
    <artifactId>maven-jira-plugin</artifactId>
    <version>${amps.version}</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Private-Package>
                com.atlassian.upm.license.storage.lib*
            </Private-Package>
            <DynamicImport-Package>
                com.atlassian.upm.api.license;version="2.0.1",
                com.atlassian.upm.api.license.entity;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>
        <productVersion>${jira.version}</productVersion>
        <productDataVersion>${jira.version}</productDataVersion>
    </configuration>
</plugin>

What is the purpose of the OSGi instructions?

You may be wondering why you have a fully functional plugin even though you've never before specified Import-Package, Private-Package, or other directives. That is because the Atlassian plugins framework does it for you. However, the licensing support code requires these two directives: Private-Package to keep the licensing support library that is included in your plugin from being visible to other plugins, and DynamicImport-Package to allow the Plugin License Support API to be detected dynamically at runtime. You do not need to specify an other OSGi instructions if you weren't already doing so.

Additionally, since we are using version 3.9+ of the maven-jira-plugin, our plugin's manifest will automatically include the Atlassian-Build-Date entry which is used to enforce license maintenance expiration.

maven-dependency-plugin usage

The generator also adds the following <plugin> to your build configuration alongside maven-jira-plugin.

<plugin>
    <!-- Include the License Storage plugin artifact such that it can be found on the classpath when we need to install it -->
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-storage-plugin</id>
            <phase>process-resources</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.outputDirectory}</outputDirectory>
                <includeArtifactIds>plugin-license-storage-plugin</includeArtifactIds>
                <stripVersion>true</stripVersion>
            </configuration>
        </execution>
    </executions>
</plugin>

This maven-dependency-plugin configuration copies the plugin-license-storage-plugin JAR artifact onto your plugin's classpath. At runtime, when your plugin requires the License Storage plugin but cannot find a sufficient version installed, your plugin is able to install it.

bundledArtifact entries

The generator adds the following <bundledArtifact> entries to the maven-jira-plugin configuration.

<plugin>
    <groupId>com.atlassian.maven.plugins</groupId>
    <artifactId>maven-jira-plugin</artifactId>
    <version>${amps.version}</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            ...
        </instructions>
        <productVersion>${jira.version}</productVersion>
        <productDataVersion>${jira.version}</productDataVersion>
        <bundledArtifacts>
            <bundledArtifact>
                <groupId>com.atlassian.upm</groupId>
                <artifactId>atlassian-universal-plugin-manager-plugin</artifactId>
                <version>1.6.3</version>
            </bundledArtifact>
            <bundledArtifact>
                <groupId>com.atlassian.upm</groupId>
                <artifactId>plugin-license-storage-plugin</artifactId>
                <version>${upm.license.compatibility.version}</version>
            </bundledArtifact>
        </bundledArtifacts>
    </configuration>
</plugin>

The first entry bundles UPM 1.6 into the plugin configuration. UPM 1.6 does not expose the licensing API, however, it is able to self-update to UPM 2.0 which does. So, this plugin version allows us to easily test both when UPM is, and is not, licensing-aware.

The second entry bundles the License Storage plugin into your plugin's configuration. In certain situations, AMPS can have problems enabling this plugin because of the dynamic nature of how the plugin is installed. This problem and workaround only affect your plugin while in its development environment; the License Storage plugin is installed and enabled just fine in a production environment.

Changes to the atlassian-plugin.xml file

The generator makes a number of changes to the descriptor file.

Enabling licensing

The generator adds the atlassian-licensing-enabled parameter to plugin-info

<param name="atlassian-licensing-enabled">true</param>

This parameter tells UPM 2.0+ that the plugin is Marketplace-paid – meaning it has a license that is UPM 2.0+ compatible.

Add component support

The Plugin License API has a number of components it depends on. These components include:

Interface

Description

PluginAccessor

Required by the embedded library.

PluginController

Required by the embedded library.

TransactionTemplate

Required by the embedded library.

ApplicationProperties

Required by the embedded library.

TemplateRenderer

Required by the embedded library.

PluginSettingsFactory

Required by the embedded library.

LoginUriProvider

Required by the embedded library.

PluginLicenseStoragePluginInstaller

Determines whether or not the License Storage plugin is needed. If the storage plugin is needed, this component installs it. Other than declaring it here, your code does not need to do anything else with it.

ThirdPartyPluginLicenseStorageManagerImpl

Used to access and manage your plugin license.

AtlassianMarketplaceUriFactoryImpl

Used to create URIs to interact with My Atlassian and generate licenses when appropriate.

The generator adds <component> elements to the atlassian-plugin.xml file for these:

<component-import key="pluginAccessor" interface="com.atlassian.plugin.PluginAccessor"/>
<component-import key="pluginController" interface="com.atlassian.plugin.PluginController"/>
<component-import key="txTemplate" interface="com.atlassian.sal.api.transaction.TransactionTemplate"/>
<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/>
<component-import key="templateRenderer" interface="com.atlassian.templaterenderer.TemplateRenderer"/>
<component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory"/>
<component-import key="loginUriProvider" interface="com.atlassian.sal.api.auth.LoginUriProvider"/>
<component-import key="userManager" interface="com.atlassian.sal.api.user.UserManager"/>
<component-import key="i18nResolver" interface="com.atlassian.sal.api.message.I18nResolver"/>
<component key="pluginLicenseStoragePluginInstaller" class="com.atlassian.upm.license.storage.lib.PluginLicenseStoragePluginInstaller"/>
<component key="thirdPartyPluginLicenseStorageManager" class="com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManagerImpl"/>
<component key="atlassianMarketplaceUriFactory" class="com.atlassian.upm.license.storage.lib.AtlassianMarketplaceUriFactoryImpl"/>

servlet Elements

The descriptor must also reference each servlet in your code. The generator adds these files in support of them:

<servlet name="License Servlet" i18n-name-key="license-servlet.name" key="license-servlet" class="com.example.plugins.tutorial.servlet.LicenseServlet">
  <description key="license-servlet.description">The License Servlet Plugin</description>
  <url-pattern>/com.example.plugins.tutorial.plugin-license-compatibility-tutorial/license</url-pattern>
</servlet>
<servlet name="License Hello World Servlet" i18n-name-key="license-hello-world-servlet.name" key="license-hello-world-servlet" class="com.example.plugins.tutorial.servlet.LicenseHelloWorldServlet">
  <description key="license-hello-world-servlet.description">The License Hello World Servlet Plugin</description>
  <url-pattern>/com.example.plugins.tutorial.plugin-license-compatibility-tutorial/licensehelloworld</url-pattern>
</servlet>
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport