Adding Activities to a Third Party feed with the Java API

Available:

Activity Streams 5.0 and later.
JIRA 5.0 and later.

On this page:

Overview

Activity Streams 5.0 includes a new, lightweight API that allows plugins to add activities to the Activity Stream. This API doesn't give you as much power as building a full-fledged Activity Provider, but it's extremely easy to use.

We'll demonstrate the API by building a sample plugin that uses a JIRA issue event listener to add an activity whenever an issue is deleted.

Source code for the complete plugin is available on Bitbucket

The sample code shown here assumes that Activity Streams 5.0 is already bundled with JIRA. The source code in the Bitbucket repository bundles Activity Streams 5 into an alpha version of JIRA 5.0. I'll clean up the discrepancy when Streams 5.0 final is bundled with JIRA

Create the plugin project

First we need to set up our development environment and create our plugin project and source directories. Assuming you have the Atlassian Plugin SDK installed, execute the following:

$ atlas-create-jira-plugin

Follow the prompts to complete the initial plugin setup. We've used com.atlassian.labs as our groupId and streams-jira-delete-issue-plugin as our artifactId.

Add dependencies to the POM

First, we need to add a dependency on the streams-thirdparty-api and streams-api artifacts to the dependencies section in our pom.xml file:

<dependency>
    <groupId>com.atlassian.streams</groupId>
    <artifactId>streams-thirdparty-api</artifactId>
    <version>5.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.streams</groupId>
    <artifactId>streams-api</artifactId>
    <version>5.0</version>
    <scope>provided</scope>
</dependency>

The scope should be provided, as the library is part of Activity Streams 5, which is already bundled in JIRA. To create an event listener, we also need to add a few other dependencies:

<dependency>
    <groupId>com.atlassian.event</groupId>
    <artifactId>atlassian-event</artifactId>
    <version>${atlassian.event.version}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.sal</groupId>
    <artifactId>sal-api</artifactId>
    <scope>provided</scope>
    <version>${sal.api.version}</version>
</dependency>

Import the ActivityService

We need to import the ActivityService component in our atlassian-plugin.xml:

<component-import key="activityService" interface="com.atlassian.streams.thirdparty.api.ActivityService"/>

We can then use constructor injection whenever we need the activityService.

Import other components

To get our event listener example working, we'll also need to inject a few other components from JIRA. We'll add the following lines to our atlassian-plugin.xml:

<component-import key="eventPublisher" interface="com.atlassian.event.api.EventPublisher"/>
<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/>

We're going to use applicationProperties to fetch the base URL of our JIRA application. We'll need the eventPublisher to subscribe to JIRA's issue events.

Create the DeleteIssueListener

We're going to create a new component that registers itself as an event listener, and responds to deletion events by posting an activity via the ActivityService. Let's first declare the component in our atlassian-plugin.xml:

<component key="userListener" class="com.atlassian.labs.streams.jira.DeleteIssueListener">
    <description>Class that processes JIRA delete issue events.</description>
</component>

As the xml snippet indicates, we'll also want to create a class called DeleteIssueListener in the com.atlassian.labs.streams package. Create that class, and add a constructor that injects the components that we need:

public class DeleteIssueListener
{
    private final EventPublisher eventPublisher;
    private final ActivityService activityService;
    private final ApplicationProperties applicationProperties;

    public DeleteIssueListener(EventPublisher eventPublisher,
                               ActivityService activityService,
                               ApplicationProperties applicationProperties)
    {
        this.eventPublisher = eventPublisher;
        this.activityService = activityService;
        this.applicationProperties = applicationProperties;
    }
}

As described in our event listener plugin tutorial, this component needs to tie its registration with the eventPublisher to our plugin's lifecycle. We can do that by implementing DisposableBean and LifecycleAware

public class DeleteIssueListener implements DisposableBean, LifecycleAware
{
   ...

    @Override
    public void destroy() throws Exception
    {
        eventPublisher.unregister(this);
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {
        eventPublisher.register(this);
    }
}

Create the activity

We'll create an activity whenever a JIRA issue is deleted, and post it using our ActivityService. The code is as follows:

@EventListener
public void onIssueDelete(IssueEvent issueEvent)
{
    if (issueEvent.getEventTypeId().equals(EventType.ISSUE_DELETED_ID))
    {
        User user = issueEvent.getUser();
        Either<ValidationErrors, Activity> result = new Activity.Builder(application("JIRA Deleted Issues", URI.create(applicationProperties.getBaseUrl())),
                                                 new DateTime(),
                                                 new UserProfile.Builder(user.getName()).fullName(user.getDisplayName())
                                                     .build())
            .content(some(html(issueEvent.getIssue().getDescription())))
            .title(some(html(user.getDisplayName() + " deleted " + issueEvent.getIssue().getKey() + " - " + issueEvent.getIssue().getSummary())))
            .url(some(URI.create(applicationProperties.getBaseUrl())))
            .build();
        for (Activity activity : result.right())
        {
            activityService.postActivity(activity);
        }
        for (ValidationErrors errors : result.left())
            log.error("Errors encountered attempting to post activity: " + errors.toString());
        }
    }
}

We create a new Activity by using the Activity.Builder helper class. Activity.Builder.build() returns Either<ValidationErrors, Activity>, which is a disjoint union : either we created a valid Activity, or we received a set of errors indicating the reasons why the Activity we tried to construct wasn't well formed. The two for loops are a clean way of handling the two cases : only one of them actually executes, so we're either going to log the errors, or post the valid activity. Hopefully, the latter is the case, and our nice new activity well appear in the feed!

Was this page helpful?
Powered by Confluence and Scroll Viewport