As of Feb 15, 2024, Atlassian Marketplace will no longer offer sales and support for server apps. We are in the process of updating the server related information in DAC to reflect this change and some of the existing information may not align with the current experience. If you're considering cloud or have migrating customers, here's the step-by-step guide to help moving your app and customers to cloud.
The Adding licensing support to server apps tutorial contains a step where users use the atlas-create-jira-plugin-module
command to put License API support into the app. This command generates a number of changes in a project. This page describes the output from this generator.
The LicenseHelloWorldServlet
class contains example of simple app functionality. The servlet displays a different message depending on whether the app has a license or not. It is best practice to execute a license validity check at every app entry point (e.g., servlet, REST resource, and webwork action). This ensures that users cannot use your app without a valid license.
Generating this example servlet is optional.
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:
Method | Details |
---|---|
| Renders the page or redirects to the login if the user is not logged in. |
|
|
| A private method that determines which Marketplace buttons are possible within the page's current state. |
| A private method that builds the page. |
| 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 app is eligible for. For example, if the app is unlicensed, Buy and Try buttons display. If the app 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 app 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 app 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
.
Both servlets have dependencies on the License API. This section details the dependency changes made by the generator.
The generator ensures that Maven imports the appropriate packages by adding the following <dependency>
entries to <dependencies>
element:
1 2<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>
The generator also edits the maven-jira-plugin
artifact's configuration in the <plugins>
section and adds some instructions. These instructions allow your app to dynamically detect the Licensing API when it is available.
These instructions are in a <plugin>
element within <plugins>
and look like the following:
1 2<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>
You may be wondering why you have a fully functional app 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 app from being visible to other apps, and DynamicImport-Package
to allow the 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 app's manifest will automatically include the Atlassian-Build-Date
entry which is used to enforce license maintenance expiration.
The generator also adds the following <plugin>
to your build configuration alongside maven-jira-plugin
.
1 2<plugin> <!-- Include the License Storage app 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 app's classpath. At runtime, when your app requires the License Storage app but cannot find a sufficient version installed, your app is able to install it.
The generator adds the following <bundledArtifact>
entries to the maven-jira-plugin
configuration.
1 2<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 app 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 app version allows us to easily test both when UPM is, and is not, licensing-aware.
The second entry bundles the License Storage app into your app's configuration. In certain situations, AMPS can have problems enabling this app because of the dynamic nature of how the app is installed. This problem and workaround only affect your app while in its development environment; the License Storage app is installed and enabled just fine in a production environment.
The generator makes a number of changes to the descriptor file.
The generator adds the atlassian-licensing-enabled
parameter to plugin-info
1 2<param name="atlassian-licensing-enabled">true</param>
This parameter tells UPM 2.0+ that the app is Marketplace-paid, meaning it has a license that is UPM 2.0+ compatible.
The License API has a number of components it depends on. These components include:
Interface | Description |
---|---|
| Required by the embedded library. |
| Required by the embedded library. |
| Required by the embedded library. |
| Required by the embedded library. |
| Required by the embedded library. |
| Required by the embedded library. |
| Required by the embedded library. |
| Determines whether or not the License Storage app is needed. If the storage app is needed, this component installs it. Other than declaring it here, your code does not need to do anything else with it. |
| Used to access and manage your app license. |
| 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:
1 2<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"/>
The descriptor must also reference each servlet
in your code. The generator adds these files in support of them:
1 2<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>
Rate this page: