Last updated Oct 27, 2023

Omit web-resources for unlicensed server apps

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.

Level of experienceINTERMEDIATE
Time estimate0:30
Applicability

Licensing apps for the Marketplace

For demonstration, this tutorial uses Jira Server 7.0, but applies to:

  • Jira Server 6.4+
  • Confluence Server 5.6+

Any product that ships with webresources 3.1.0+

Tutorial overview

This tutorial will show you how to exclude resources from the front end when your app is unlicensed. Omitting these files will improve the performance of the pages that your app affects, when the license is missing.

Required knowledge

Concepts covered

This tutorial will cover the UrlReadingCondition interface--what it does, and why it is useful.

These instructions are based on Mac OS X, but you can use any supported operating system. Similarly, any supported IDE can be used. Just use the equivalent operations for your specific environment.

This tutorial was last tested with webresources 3.1.0 and Jira 6.4.

App source

We encourage you to work through this entire tutorial. However, if you want to skip ahead or check your work when you have finished, you can find the app source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, enter the following command:

1
2
$ git clone git@bitbucket.org:atlassian_tutorial/tutorial-licensed-conditions.git

Alternatively, you can download the source as a ZIP archive by choosing Download repository here: https://bitbucket.org/atlassian_tutorial/tutorial-licensed-conditions/downloads

Step 1. Start up your host product

In this step, you'll start up a product instance to see your app in the Universal Plugin Manager (UPM).

  1. Ensure you're in your app directory. If you followed the steps above exactly, your directory should be tutorial-licensed-conditions

    1
    2
    cd tutorial-licensed-conditions/
    
  2. Issue the following command to start up Jira: 

    1
    2
    atlas-run --product jira --version 6.4 
    

    The Jira URL will be displayed in your terminal's output when finished, usually within a few minutes. 

    1
    2
    [INFO] jira started successfully in 42s at http://localhost:2990/jira
    [INFO] Type Ctrl-D to shutdown gracefully
    [INFO] Type Ctrl-C to exit
    
  3. Navigate to your Jira instance, usually at http://localhost:2990/jira.

  4. Log in with the credentials admin/admin.

  5. Navigate to the icon ** > Add-ons**. 

  6. Click Manage apps in the left-nav.

You can also access this page directly at `http://localhost:2990/jira/plugins/servlet/upm`.
1. Verify that you can see your app listed in the UPM.

Step 2. Add a web-resource to your app

Here, we'll add a web-resource to the app that adds a flag to the view issue page, letting them know our app is licensed. Normally this kind of thing will irritate and annoy your end-users, so it's probably best to only use this code for demonstrating and testing web-resource conditions. 

  1. Open your app project.

  2. Open your atlassian-plugin.xml descriptor from the /src/main/resources directory.

  3. Somewhere before the closing </atlassian-plugin> tag, add a web-resource definition:

    1
    2
    <web-resource key="tutorial-view-issue-resources" name="tutorial-licensing Web Resources for the View Issue page">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        <resource type="download" name="issue.css" location="/css/issue.css"/>
        <resource type="download" name="issue.js" location="/js/issue.js"/>
        <context>jira.view.issue</context>
    </web-resource>
    
  4. Save and close the atlassian-plugin.xml descriptor file. 

    Your descriptor file should look like this:

    1
    2
    <atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
        <plugin-info>
            <description>${project.description}</description>
            <version>${project.version}</version>
            <vendor name="${project.organization.name}" url="${project.organization.url}" />
            <param name="plugin-icon">images/pluginIcon.png</param>
            <param name="plugin-logo">images/pluginLogo.png</param>
            <param name="atlassian-licensing-enabled">true</param>
        </plugin-info>
        <!-- add our i18n resource -->
        <resource type="i18n" name="i18n" location="tutorial-licensing"/>
        
        <!-- add our web resources -->
        <web-resource key="tutorial-licensing-resources" name="tutorial-licensing Web Resources">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            
            <resource type="download" name="tutorial-licensing.css" location="/css/tutorial-licensing.css"/>
            <resource type="download" name="tutorial-licensing.js" location="/js/tutorial-licensing.js"/>
            <resource type="download" name="images/" location="/images"/>
            <context>tutorial-licensing</context>
        </web-resource>
    
        <web-resource key="tutorial-view-issue-resources" name="tutorial-licensing Web Resources for the View Issue page">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
    
            <resource type="download" name="issue.css" location="/css/issue.css"/>
            <resource type="download" name="issue.js" location="/js/issue.js"/>
            <context>jira.view.issue</context>
        </web-resource>
        <!-- publish our component -->
        <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
            <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
        </component>
    
        <!-- import from the product container -->
        <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
        <component-import key="pluginLicenseManager" interface="com.atlassian.upm.api.license.PluginLicenseManager"/>
    
    </atlassian-plugin>
    
  5. Create an issue.css file in the /src/main/resources/css directory.

  6. Create an issue.js file in the /src/main/resources/js directory. Open the file and add the following code to add a flag to the page: 

    1
    2
    // You can read about how AMD works here: https://github.com/amdjs/amdjs-api/wiki/AMD
    require([
        'jira/flag',
        'jquery'
    ], function(
        flag,
        $
    ) {
        var titleText = "MyPlugin is licensed!";
        var messageText = "I just thought you'd like to know that.";
        $(function() {
            flag.showInfoMsg(titleText, messageText);
        });
    });
    
  7. Save and close the issue.js file.

  8. The QuickReload app will automatically pick up your resources changes.

  9. Navigate to an issue page in Jira.

  10. Verify that the contents of the issue.css and issue.js files are output on the page, via your browser's console. At this point, since we're using atlas-run, they will be served as individual files. 

    unconditional resource on viewissue

Your resource has now been added to the view issue page. However, note that the flag's message is inaccurate; the app is outputting the code irrespective of whether the app is licensed or not. Let's change that!

Step 3. Create a new ConditionEvaluator class

Currently, your web-resource is being added to the view issue page irrespective of whether your app is licensed or not.

Your app may already have licensing rules. How you choose to license your app is beyond the scope of this tutorial. Here, you will implement a ConditionEvaluator class which will be a proxy to your app's licensing logic.

ConditionEvaluator.java

1
2
package com.example.plugins.conditions;

public interface ConditionEvaluator
{
    public boolean evaluate(ConditionType type);
}

ConditionType.java

1
2
package com.example.plugins.conditions;
 
public enum ConditionType
{
    LICENSED;
}

ConditionEvaluatorImpl.java

1
2
package com.example.plugins.conditions;
 
import com.example.plugins.conditions.ConditionEvaluator;

public class ConditionEvaluatorImpl implements ConditionEvaluator
{
    @Override
    public boolean evaluate(ConditionType type)
    {
        switch (type)
        {
            case LICENSED:
                return isLicenseValid();
            default:
                return false;
        }
    }
    private boolean isLicenseValid()
    {
        // Implement your logic for determining if
        // your app has a valid, active license.
    }
}

Your app should be importing the PluginLicenseManager from UPM. If not, refer to the previous tutorial: Adding licensing support to server apps.

You can implement your license check in the isLicenseValid() method. Refer to the Server app licensing documentation for more information on licensing.

Step 4. Add a component declaration for ConditionEvaluator to your app

The ConditionEvaluator class has been built, but currently it cannot be used in other classes in your app. That's because Spring and OSGi don't know about the component yet. In order to inject it in to your other classes and components, you'll need to add a <component> entry to your atlassian-plugin.xml file.

  1. Open your  atlassian-plugin.xml  descriptor from the  /src/main/resources  directory.

  2. Somewhere before the closing </atlassian-plugin> tag, add a web-resource definition:

    1
    2
    <web-resource key="tutorial-view-issue-resources" name="tutorial-licensing Web Resources for the View Issue page">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        <resource type="download" name="issue.css" location="/css/issue.css"/>
        <resource type="download" name="issue.js" location="/js/issue.js"/>
        <context>jira.view.issue</context>
    </web-resource>
    
  3. Save and close the atlassian-plugin.xml descriptor file. Your descriptor file should look like this:

    1
    2
    <atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
        <plugin-info>
            <description>${project.description}</description>
            <version>${project.version}</version>
            <vendor name="${project.organization.name}" url="${project.organization.url}" />
            <param name="plugin-icon">images/pluginIcon.png</param>
            <param name="plugin-logo">images/pluginLogo.png</param>
            <param name="atlassian-licensing-enabled">true</param>
        </plugin-info>
        <!-- add our i18n resource -->
        <resource type="i18n" name="i18n" location="tutorial-licensing"/>
        
        <!-- add our web resources -->
        <web-resource key="tutorial-licensing-resources" name="tutorial-licensing Web Resources">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            
            <resource type="download" name="tutorial-licensing.css" location="/css/tutorial-licensing.css"/>
            <resource type="download" name="tutorial-licensing.js" location="/js/tutorial-licensing.js"/>
            <resource type="download" name="images/" location="/images"/>
            <context>tutorial-licensing</context>
        </web-resource>
    
        <web-resource key="tutorial-view-issue-resources" name="tutorial-licensing Web Resources for the View Issue page">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            <resource type="download" name="issue.css" location="/css/issue.css"/>
            <resource type="download" name="issue.js" location="/js/issue.js"/>
            <context>jira.view.issue</context>
        </web-resource>
     
        <!-- publish our components -->
        <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
            <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
        </component>
        <component key="myPluginConditionEvaluator" class="com.example.plugins.conditions.ConditionEvaluatorImpl" public="false">
            <interface>com.example.plugins.conditions.ConditionEvaluator</interface>
        </component>
    
        <!-- import from the product container -->
        <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
        <component-import key="pluginLicenseManager" interface="com.atlassian.upm.api.license.PluginLicenseManager"/>
    
    </atlassian-plugin>
    

    This condition evaluator can now be used in your condition implementation.

Step 5. Create a new IsPluginLicensedCondition class

Currently, your web-resource is being added to the view issue page irrespective of whether your app is licensed or not. This adds unnecessary resources to the page that slow down the application. Here, you will implement a condition that checks your app's licensed status, which you can use to control when your app's web-resources are visible.

Create a new Java class called IsPluginLicensedCondition. This class should extend the webresource framework's SimpleUrlReadingCondition class.

IsPluginLicensedCondition.java

1
2
package com.example.plugins.conditions;

import com.atlassian.plugin.webresource.condition.SimpleUrlReadingCondition;

public class IsPluginLicensedCondition extends SimpleUrlReadingCondition
{
    private ConditionEvaluator conditionEvaluator;

    public IsPluginLicensedCondition(final ConditionEvaluator conditionEvaluator)
    {
        this.conditionEvaluator = conditionEvaluator;
    }

    @Override
    protected boolean isConditionTrue()
    {
        return conditionEvaluator.evaluate(ConditionType.LICENSED);
    }

    @Override
    protected String queryKey()
    {
        // This string will be appended to URLs as a GET parameter
        // whenever 'isConditionTrue' returns true.
        // You should make the string short, but unique to your app.
        return "myPluginName";
    }
}

The SimpleUrlReadingCondition class is provided by the atlassian-plugins-webresource framework, and implements the UrlReadingCondition interface. Conditions that implement this interface are allowed to participate in the batching process, since they are able to affect their parent web-resource's URLs.

Step 6. Add the license check condition to your web-resource

Now your app has a condition to toggle the output of your web-resources, it's time to make use of it.

  1. Open your app's atlassian-plugin.xml file.

  2. Add a <condition> to each of your web-resources that should only be active when the app is licensed:

    1
    2
    <condition class="com.example.plugins.conditions.IsPluginLicensedCondition" />
    
  3. Save and close the atlassian-plugin.xml file. Your descriptor file should look like this: 

    1
    2
    <atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
        <plugin-info>
            <description>${project.description}</description>
            <version>${project.version}</version>
            <vendor name="${project.organization.name}" url="${project.organization.url}" />
            <param name="plugin-icon">images/pluginIcon.png</param>
            <param name="plugin-logo">images/pluginLogo.png</param>
            <param name="atlassian-licensing-enabled">true</param>
        </plugin-info>
        <!-- add our i18n resource -->
        <resource type="i18n" name="i18n" location="tutorial-licensing"/>
        
        <!-- add our web resources -->
        <web-resource key="tutorial-licensing-resources" name="tutorial-licensing Web Resources">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            <resource type="download" name="tutorial-licensing.css" location="/css/tutorial-licensing.css"/>
            <resource type="download" name="tutorial-licensing.js" location="/js/tutorial-licensing.js"/>
            <resource type="download" name="images/" location="/images"/>
            <context>tutorial-licensing</context>
        </web-resource>
    
        <web-resource key="tutorial-view-issue-resources" name="tutorial-licensing Web Resources for the View Issue page">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            <resource type="download" name="issue.css" location="/css/issue.css"/>
            <resource type="download" name="issue.js" location="/js/issue.js"/>
            <context>jira.view.issue</context>
            <condition class="com.example.plugins.conditions.IsLicensedCondition" />
        </web-resource>
    
        <!-- publish our component -->
        <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
            <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
        </component>
        <component key="myPluginConditionEvaluator" class="com.example.plugins.conditions.ConditionEvaluatorImpl" public="false">
            <interface>com.example.plugins.conditions.ConditionEvaluator</interface>
        </component>
    
        <!-- import from the product container -->
        <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
        <component-import key="pluginLicenseManager" interface="com.atlassian.upm.api.license.PluginLicenseManager"/>
    </atlassian-plugin> 
    

Excellent! It's time to test whether the condition works.

Step 7. Verify the result in various licensed states

Here you'll check whether everything is wired properly, by inputting a test license to check that the web-resource behaves the way it should.

Check behavior in unlicensed state

To check the behavior in an unlicensed state:

  1. Navigate to the UPM in Jira and find your app.
  2. Edit the license key field, and remove the license.
  3. Click Update.
  4. Your app should now be "Unlicensed." UPM app unlicensed
  5. Navigate to an issue page in Jira.
  6. Verify that the contents of the issue.css and issue.js files are not output to the page, via your browser's console. The files should be empty. conditional resource no license

Great! Our web-resource contents aren't affecting the page when we are not licensed!

Add a license to the app

To add a license to the app:

  1. Navigate to the UPM in Jira and find your app.

  2. Enter the following in the License key field:

    1
    2
    AAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9
    mbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV
    r7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j
    TapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8
    k/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC
    dutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt
    

    This is a 60-second license key.

  3. Click Update.

  4. If accepted, a lozenge and a banner should appear to notify you that your app license expires soon: UPM app licensed with timebomb

  5. Navigate to an issue page.

  6. Verify that the contents of the issue.css and issue.js files are output on the page, via your browser's console. At this point, since we're using atlas-run, they will be served as individual files.  conditional resource licensed source conditional resource licensed network

Your app will now omit web-resources when your app is unlicensed!

Further testing notes

  • Test other license states using Timebomb licenses for testing.
  • If you run out of licenses when using atlas-run or atlas-debug, you can shut down your Jira instance by clicking CTRL+D in your terminal; and then running atlas-mvn clean to remove your target staging directory. 

Rate this page: