Friday, 29 June 2012

Implementation of Rest service in Jira platform

REpresentational State Transfer (REST) is a style of software architecture for distributed systems such as web applications. REST has increasingly replaced other design models such as SOAP and WSDL due to its simpler style. Jira platform also gave a hook to create Rest service in it's architecture. Let's look, how to create a Rest service in Jira platform.

Problem Statement: Populate the list of priority of jira application using rest service.

1. First and the foremost point is, rest services can be created/implemented only in osgi plug-in (type-2) of jira.

Please add following xml code snippet in atlassian-plugin.xml file of type-2 plugin.

<!--             Application Rest Service             -->
    <rest key="app-rest" name="Application REST API" path="/application"    version="1.0">
        <description>Provides REST end points used in Application.</description>
    </rest>

2. Add a data source which would do the population of required object.


package com.application.company.gadget;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;


@Path("/fields")
@Produces(MediaType.APPLICATION_JSON)
public class CustomFieldDataResource extends AbstractGadgetResource {

    @GET
    @Path("/priority")
    public Keys getPriority() {
        List<String> priorities = this.getPriorities();
        Collection<Key> items = new ArrayList<Key>();
        for (final String priority : priorities) {
            items.add(new Key(priority, priority));
        }
        return new Keys(items);
    }
    
    private List<String> getPriorities() {
        Collection<Priority> systemPriorities = ComponentManager.getInstance().getConstantsManager()
                .getPriorityObjects();
        List<String> priorities = new ArrayList<String>();
        for (Priority priority : systemPriorities) {
            String priorityName = priority.getName();
            priorities.add(priorityName);
        }
        return priorities;
    }

   
    @XmlRootElement
    public class Keys {
        @SuppressWarnings("unused")
        @XmlElement
        private Collection<Key> keys;

        public Keys(Collection<Key> keys) {
            this.keys = keys;
        }
    }

    @XmlRootElement
    public class Key {
        @SuppressWarnings("unused")
        @XmlElement
        private String label;

        @SuppressWarnings("unused")
        @XmlElement
        private String value;

        public Key() {
        }

        Key(String label, String value) {
            this.label = label;
            this.value = value;
        }
    }

}


3. Now, build your type-2 plugin in jira platform and try to validate rest service.

Way to validate the rest service in generic form: 
http://<machine name>:<port-number>/<context>/rest/<rest-tag name>/1.0/<data-source-name>/<method/<behaviour name>

In our case, http://localhost:8887/jira/rest/application/1.0/fields/priority

You would see below response: 
{
  • "keys": [
    • {
      • "label": "P1",
      • "value": "P1"
      },
    • {
      • "label": "P2",
      • "value": "P2"
      },
    • {
      • "label": "P3",
      • "value": "P3"
      },
    • {
      • "label": "P4",
      • "value": "P4"
      },
    • {
      • "label": "P5",
      • "value": "P5"
      },
    • {
      • "label": "P6",
      • "value": "P6"
      },
    • {
      • "label": "Not Specified",
      • "value": "Not Specified"
      }
    ]
}


How to add a Listner in Jira platform

Though I have started blogging some time back in feb'12, I was not consistent in my posts. But, now onwards, I will try to post about jira platform consistently. Today, I will share about how listener can be configured in jira platform.

As literally speaking, LISTENER is some body/thing who listens and does the job based on what has happened on a particular event. It's the same on jira platform as well.

Jira has given one abstraction for the listener, AbstractIssueEventListener.java So, if you want to create a listener for your jira platform, you need to extend this class and implement the required events. Once you deploy your plug-in on the jira platform, by default this listener will be configured to your jira platform. You may see this on the administration section of jira application.


Ex: ApplicationListene.java

import com.atlassian.jira.event.issue.AbstractIssueEventListener;

public class ApplicationListener extends AbstractIssueEventListener {

   
    Logger logger = Logger.getLogger(ApplicationListener.class);
   
    @Override
    public void issueCreated(IssueEvent event) {
                   // do what ever you want do on create issue event
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of issue creation: ", e);
        }
    }
   
    @Override
    public void issueUpdated(IssueEvent event) {
        try {
              // what ever you want to do on the issue update         
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of issue update: ", e);
        }
    }
   
    @Override
    public void issueDeleted(IssueEvent event) {
        super.issueDeleted(event);
        try {
           // do some thing
           
        } catch (Exception e) {
            this.logger.error("Exception Caught while listening the event of issue delete", e);
        }
    }

    @Override
    public void issueMoved(IssueEvent event) {
        try {
            // what ever you want to do on moving issue event
        } catch (Exception e) {
            this.logger.error("Exception Caught while listening the event of issue move: ", e);
        }
    }

    @Override
    public void issueResolved(IssueEvent event) {
        try {
              // do some thing on issue resolution
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of issue resolve :", e);
        }
    }

    @Override
    public void issueClosed(IssueEvent event) {
        try {
             // do some thing on close event of the issue
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of issue close :", e);
        }
    }
   
    @Override
    public void issueReopened(IssueEvent event) {
        try {
             // do some thing on when issue is re-opened
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of issue reopen :", e);
        }
    }
   
    @Override
    public void issueWorklogDeleted(IssueEvent event) {
        try {
              // do some thing on when work log is deleted on the issue          
        } catch (Exception e) {
            logger.error("Exception Caught while listening the event of worklog deletion on issue :", e);
        }
    }

    @Override
    public void issueWorkLogged(IssueEvent event) {
        // Implement this to do some action on when work is logged on issue
    }

    @Override
    public void issueWorklogUpdated(IssueEvent event) {
        // Implement this to do some action on when worklog is updated on issue
    }
   
    @Override
    public void issueGenericEvent(IssueEvent event) {
        // Implement this to do some action on Issue Generic Event
    }
   
    @Override
    public void issueAssigned(IssueEvent event) {
        // Implement this to do some action on Issue Assigned Event
    }

    @Override
    public void issueCommented(IssueEvent event) {
        // Implement this to do some action on Issue Commented Event
    }

    @Override
    public void issueCommentEdited(IssueEvent event) {
        // Implement this to do some action on Issue Comment Edit event
    }
   
 }


Friday, 22 June 2012

How to add a jira service plugin in Jira platform

This post is about how to write a service in JIRA platform, which is responsible for executing a periodically task. One example could be pushing meta information of projects from jira platform to a stand-alone database for analysis or any other purpose.

I am assuming that, reader is familiar with development of type-2 plug-ins in jira platform.

Problem statement: Push meta information of projects in Jira platform to a stand-alone database.

1. SAL scheduler component needs to be imported into customized jira plugin by including following xml code snippet into atlassian-plugin.xml file.


<component-import key="pluginScheduler">
        <description>Atlassian SAL Scheduler</description>
        <interface>com.atlassian.sal.api.scheduling.PluginScheduler</interface>
</component-import> 



2. Create an abstract service class "DatabaseSyncService.java"


package com.company.application.schedule;

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.sal.api.scheduling.PluginScheduler;
import com.
company.application.ao.service.EventLogService;

/**
 *
 *
 * @author sateesh.b
 *
 */
public abstract class DatabaseSyncService {

    private final PluginScheduler pluginScheduler;  // provided by SAL
    private ActiveObjects activeObject;
    private EventLogService eventLogService;
   
    /**
     * Constructor
     */
    public DatabaseSyncService(PluginScheduler pluginScheduler, ActiveObjects activeObject,
            EventLogService eventLogService) {
        this.pluginScheduler = pluginScheduler;
        this.activeObject = activeObject;
        this.eventLogService = eventLogService;
    }
   
    public ActiveObjects getActiveObject() {
        return this.activeObject;
    }
   
    public EventLogService getEventLogService() {
        return this.eventLogService;
    }
   
    public PluginScheduler getPluginScheduler() {
        return this.pluginScheduler;
    }
}
 


3. Sync Service interface

package com.company.application.schedule;

/**
 * This interface is used to schedule the services added in the application.
 *
 * @author sateesh.b
 *
 */
public interface SyncService {
   
    public void reschedule(long interval);
   
}


4. Create class which would register the service with the Jira Plugin Scheduler, so that it would run as a periodical service. This class should implement interface LifecycleAware, so that it would get called when this plugin is deployed in application server.


package com.company.application.schedule;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.sal.api.lifecycle.LifecycleAware;
import com.atlassian.sal.api.scheduling.PluginScheduler;
import com.
company.application.ao.service.EventLogService;
import com.
company.application.core.util.PropertyHelper;

/**
 * This is the component class, which adds <b>"Projects Meta Database Sync"</b> service to the
 * <code>JiraPluginScheduler</code>
 * 
 * This class is executed at the time of server startup
 * @author sateesh.b
 *
 */
public class
ProjectsMetaDatabaseSyncServiceImpl extends DatabaseSyncService implements SyncService, LifecycleAware {

    static final String KEY =
ProjectsMetaDatabaseSyncServiceImpl.class.getName() + ":instance";
    private static final String JOB_NAME = "
Projects Meta Info Database Sync";
   
    /**
     * constructor
     */
    public
ProjectsMetaDatabaseSyncServiceImpl (PluginScheduler pluginScheduler,
            ActiveObjects activeObject, EventLogService eventLogService) {
        super(pluginScheduler, activeObject, eventLogService);
    }
   
    /** 
     * Registering the "Projects Meta Info Database Sync" service in the application.
     * This service will run periodically with an interval of 1 day
     *
     */
    @Override
    public void reschedule(long interval) {
        getPluginScheduler().scheduleJob(JOB_NAME, ProjectsMetaInfoDatabaseSyncTask.class, new HashMap<String,Object>() {{
            put(KEY,
ProjectsMetaDatabaseSyncServiceImpl .this);}}, getServiceStartupDate(), interval);       
    }
   
   
    /** 
     * this method will be called at the start of the instance
     */
    @Override
    public void onStart() {
        //final String interval = PropertyHelper.getInstance().getPropertyValue("database.sync.interval");
        // interval information is fetched from property file
        final String interval = "60";        reschedule(Long.valueOf(interval));
    }   
   
    private Date getServiceStartupDate() {
        Calendar cal = Calendar.getInstance();
        GregorianCalendar gCal = new GregorianCalendar(cal.get(cal.YEAR), cal.get(cal.MONTH),
                cal.get(cal.DAY_OF_MONTH)+1, 0, 5);
        /*GregorianCalendar gCal = new GregorianCalendar(cal.get(cal.YEAR), cal.get(cal.MONTH),
                cal.get(cal.DAY_OF_MONTH), 15, 20);*/
        return gCal.getTime();
    }
}




5. Create a class which will handle the business logic of executing the repeatable task. This class needs to implement PluginJob.java interface as mentioned below. The below class will be invoked by the Jira Plugin Scheduler and "execute" behavior will be called with an interval mentioned in the class "ProjectsMetaDatabaseSyncServiceImpl.java"


/**
 *
 */
package com.company.application.schedule;

import java.sql.SQLException;
import java.util.Map;

import org.apache.log4j.Logger;

import com.atlassian.sal.api.scheduling.PluginJob;

/**
 * This <code>
ProjectsMetaInfoDatabaseSyncTask</code> class would connect to jira database to get the
 * jira project configurations and will be sync with stand-alone database
 *
 * This class is invoked by the <code>ServiceManager</code> of Jira framework.
 *
 * @author sateesh.b
 *
 */
public class
ProjectsMetaInfoDatabaseSyncTask implements PluginJob {
   
    private static Logger logger = Logger.getLogger(
ProjectsMetaInfoDatabaseSyncTask.class);
    private static boolean isRunning = false;
   
    /**
     * this method is invoked by the Jira Service manager
     */
    public void execute(Map<String, Object> jobDataMap) {
        if (!isRunning) {
            isRunning = true;
            try {
               
ProjectsMetaDatabaseSyncServiceImpl monitor = (ProjectsMetaDatabaseSyncServiceImpl) jobDataMap.get(ProjectsMetaDatabaseSyncServiceImpl.KEY);
                assert monitor != null;
            
            // now we can access the monitor object (ProjectsMetaDatabaseSyncServiceImpl) and write the
            // needful logic to get the project info and push them to stand-alone database.
            // errors can be logged into jira database 
                              
            } catch (ClassNotFoundException e) {
                logger.error("Unable to run database sync due to driver exception", e);
            } catch (SQLException e) {
                logger.error("Unable to run database sync due to sql exception", e);
            } finally {
                isRunning = false;
            }
        }
    }
}
 


6. Finally, we need to add the component/service plugin information into atlassian-plugin.xml file
Don't forget to mention this component as public (by mentioning public="true"), then only this component will be available with jira plugin service component.


   <component key="database-sync" class="com.company.application.schedule.ProjectsMetaDatabaseSyncServiceImpl"
             system="true" public="true">
        <description>The plugin component that schedules the projects meta info sync process.</description>
        <interface>com.atlassian.sal.api.lifecycle.LifecycleAware</interface>
        <interface>com.company.application.schedule.SyncService</interface>
    </component>