In Part 1 of this blog post series, I introduced Unified AppLinks and motivations behind it, and also showed how simple it is for plugin developers to consume user configured AppLinks to make authenticated requests on remote services. In this post, we will look at how to extend AppLinks to implement our own application types to integrate to non Atlassian services.

Application Type


Continuing with our Twitter theme, the first thing we are going to implement is a Twitter application type. A custom application type is a plugin module that provides a class and a manifest. The class is used, as seen in part one, by consumers of the AppLink, to look up AppLinks of that type. This is the first thing we’ll implement.

public class TwitterApplicationType implements NonAppLinksApplicationType {

Note the interface that is being implemented here. The type is a NonAppLinksApplicationType. This may sound a little unintuitive at first. NonAppLinksApplicationType indicates that this application type is not an application that supports AppLinks. Twitter, of course, does not run AppLinks, there is no AppLinks plugin installed in Twitter. In contrast, Jira, Confluence and other Atlassian products do support AppLinks. The implementation of NonAppLinksApplicationType indicates to AppLinks that it can’t use AppLinks specific REST APIs on this application. So generally, when implementing custom application types, NonAppLinksApplicationType is the interface to implement.

The first method we’ll implement in this interface is getId(). This returns a TypeId, which uniquely identifies that application type. We’ll store this type ID in a constant:

static final TypeID TYPE_ID = new TypeId(“twitter”);
public TypeId getId() {
return TYPE_ID;
}

The two other methods are fairly straight forward, and can be seen here:

public String getI18nKey() {
return “twitter”;
}
public URI getIconUrl() {
return URI.create(“http://twitter.com/phoenix/favicon.ico”);
}

At this point, if you have a look at the example code I’ve published, you’ll see that I’ve implemented an additional interface, StaticUrlApplicationType. This interface has an additional method, getStaticUrl(), which allows services for which there is only ever one instance to declare a URL that they live at. Twitter is an example of such a service, there is only one Twitter, and it only has one URL (or rather, set of URLs) to contact it. In contrast, if you are integrating with an application that users download and install on their own servers, you would implement NonAppLinksApplicationType as above. Application types that implement StaticUrlApplicationType can be configured without the need for users to enter a URL.

Manifest Producer


While the application type is used by other plugins to look up AppLinks of that type, another interface must be implemented for the purpose of AppLinks knowing more about this application type, this is the ManifestProducer interface. A manifest producer can be used to fetch information about the application at the user configured URI. It is used when users are configuring AppLinks, to tell users a bit about the remote application, as well as to define what authentication types the application supports. It produces a Manifest, which has information about what version of the application is installed, what the name of that instance is, an ID for the instance, and so on. Making requests to fetch this information from the remote application is optional, you may also hard code it. In the case of a service like Twitter, we know all the necessary information without having to fetch anything, so we’re going to hard code it.

We start by creating a new class called TwitterManifestProducer:

public class TwitterManifestProducer implements ManifestProducer {

The easiest method to implement is getStatus(URI). For a service like Twitter, it is usually safe to assume it is available, so we’ll implement it like so:

public ApplicationStatus getStatus(URI url) {
return ApplicationStatus.AVAILABLE;
}

Our implementation of getManifest(URI) is going to use an anonymous inner class:

public Manifest getManifest(final URI url) throws ManifestNotFoundException {
return new Manifest() {

};
}

We’ll now implement the getId() method. getId() should produce a globally unique ID for that application instance. In most cases, it should be sufficient to simply use the user entered URL to identify the application, unless the remote application does have a concept of a unique ID for itself that can be retrieved:

public ApplicationId getId() {
return ApplicationIdUtil.generate(url);
}

The manifest has a getUrl() method which could be used if the remote application has a different idea of what it’s base URL is to what the user has entered (as may be the case with services served from multiple virtual hosts), however usually returning the passed in URL that the user entered is sufficient:

public URI getUrl() {
return url;
}
getInboundAuthenticationTypes() and getOutboundAuthenticationTypes() are used to specify what types of authentication this application supports. The inbound authentication types are the types supported when making requests into that application, so when we make a request in to Twitter, these are the authentication types it will support. The outbound authentication types are the types supported by the application when it makes requests out to us, so if Twitter had support for making requests on us (which it doesn’t), this could be used to declare those. The list of authentication types that get returned here control what the user is given to configure. For example, if BasicAuthenticationProvider is returned, the user will be able to configure a username and password, if OAuthAuthenticationProvider is returned, the user will be able to enter OAuth details. For the outbound types, this would allow the user to configure an OAuth consumer key, for example, for the remote application to use.

In our case, we are going to use a custom authentication type, called TwitterOAuthAuthenticationProvider. There is no reason why we couldn’t return OAuthAuthenticationProvider here, doing so would mean a lot less work, but I’m implementing a custom one for Twitter for the purposes of demonstrating how to implement one. This I will do in Part 3 of this series. If you’re coding this yourself and want something that will work immediately, you may use the OAuthAuthenticationProvider instead.

public Set<Class> getInboundAuthenticationTypes() {
return Collections.<Class>singleton(TwitterOAuthAuthenticationProvider.class);
}
public Set<Class> getOutboundAuthenticationTypes() {
return Collections.emptySet();
}

The remaining methods to implement are straight forward. The name is the name of the remote application, this can be hard coded or can be retrieved from the service. The type ID is the type ID that we created in the ApplicationType. The version and build number are optionally implemented if you wish to report to the user what version of the app is running remotely. The AppLinks version property is only for applications that run AppLinks, and the public signup property is used to tell the user they have the option of signing up to that service.

public String getName() {
return “Twitter”;
}
public String getVersion() {
return null;
}
public Long getBuildNumber() {
return 0L;
}
public Version getAppLinksVersion() {
return null;
}
public Boolean hasPublicSignup() {
return false;
}

Finally, we need to declare our application type and manifest producer in our plugin descriptor, atlassian-plugin.xml:

Now we’ve implemented our custom application type, and can install it into an app. This application type will work in any Atlassian application.

Hash Tag Entity Type


In Part 1, I showed how a plugin may consume an entity link that linked a Confluence space to a Twitter hash tag. Entities, like application types, are extendable in AppLinks, so we’ll now implement a TwitterHashTagEntityType. Like the TwitterApplicationType, this will implement an interface called NonAppLinksEntityType:

public class TwitterHashTagEntityType implements NonAppLinksEntityType {
Like the ApplicationType, an EntityType must also have a unique type ID, which we implement here:
static final TypeId TYPE_ID = new TypeId(“twitter.hash.tag”);
public TypeId getId() {
return TYPE_ID;
}

An entity type also has a method for looking up a display URL. Given an ApplicationLink, and an entity key (in our case this will be the hash tag), we can implement this method to generate a link to the remote entity. In our case we’re returning a link to a search request for that hash tag:

public URI getDisplayUrl(ApplicationLink applicationLink, String tag) {
return URI.create(“http://twitter.com/#!/search?q=” + URLEncoder.encode(“#” + tag));
}

The remaining properties are straight forward, we supply i18n keys for the various forms of the name of the entity, a URL to an icon for the entity type, and the class for the application type that this entity type is for:

public Class getApplicationType() {
return TwitterApplicationType.class;
}
public String getShortenedI18nKey() {
return “twitter.hash.tag”;
}
public String getI18nKey() {
return “twitter.hash.tag”;
}
public String getPluralizedI18nKey() {
return “twitter.hash.tags”;
}
public URI getIconUrl() {
return URI.create(“http://twitter.com/phoenix/favicon.ico”);
}

Last of all, we modify our plugin descriptor to declare our entity type:

Exporting OSGi classes


If you are implementing your custom application/entity type embedded within the plugin that uses it, you don’t necessary have to do this step, though it is still encouraged. If you have implemented a separate plugin for the types, so that other plugins can all use the same type, then you will need to export the above classes so that other plugins can use them to consume AppLinks of those types. If you’ve used the Atlassian SDK to create your plugin you should, in the plugins pom.xml file, have the maven plugin for the product you’re working in already declared, so you can add configuration to it like so:

com.atlassian.maven.plugins
maven-refapp-plugin
3.2.2
true

com.atlassian.applinks.plugins.twitter

Next steps


Now we’ve implemented our custom application and entity types. In many cases, we’re now finished, assuming the authentication providers that come with AppLinks out of the box suit our needs. However, sometimes there is additional configuration that we may want to allow a user to configure that is specific to a type of application, and in some cases we may need a custom authentication provider. In Part 3 I will explain both custom authentication providers, and how to extend the UI to provide additional configuration screens for AppLinks.

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now