Tuesday 17 July 2012

Create a post function which needs to be called on Jira workflow transition.

Have you ever wondered, why there are few post functions available in the Jira platform while transitioning between statuses of an issue workflow?  Of course, I felt while I am working with Jira platform.

Today we will look how we can add post function which can be listed in transition states of jira workflow. Fyi, Jira platform uses Open Symphony (OS) workflow api in it's architecture which will take care of  issue transition states. To know more about OS workflow API please visit here

Following are the various steps required to implement to create a post function.

1. Create a class "ModifyCustomFieldPF" which incorporates the implementation of business logic. Whenever this post-function is called, execute behavior will be executed by the Jira platform and it's implementation class "ModifyCustomFieldPFImpl" as shown below.

package com.company.application.module.postfunction;

import java.util.Map;

import com.atlassian.jira.issue.MutableIssue;

import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.FunctionProvider;
import com.opensymphony.workflow.WorkflowException;

public class ModifyCustomFieldPF implements FunctionProvider {
      public static final String ISSUE = "issue";

      public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException {
            MutableIssue mutableIssue = (MutableIssue) transientVars.get(ISSUE);
            // here implement the required business logic
      }
}



package com.company.application.module.postfunction;


import java.util.HashMap;
import java.util.Map;

import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory;
import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory;
import com.opensymphony.workflow.loader.AbstractDescriptor;

public class ModifyCustomFieldPFImpl extends AbstractWorkflowPluginFactory implements WorkflowPluginConditionFactory

{
   
    public ModifyCustomFieldPFImpl() {
        super();
        // TODO Auto-generated constructor stub
    }

    protected void getVelocityParamsForEdit(Map arg0, AbstractDescriptor arg1) {
        // TODO Auto-generated method stub

    }

    protected void getVelocityParamsForInput(Map arg0) {
        // TODO Auto-generated method stub

    }

    protected void getVelocityParamsForView(Map arg0, AbstractDescriptor arg1) {
        // TODO Auto-generated method stub

    }

    public Map getDescriptorParams(Map arg0) {
        // TODO Auto-generated method stub
        return new HashMap();
    }

}   



2. Create a velocity template(ModifyCustomFieldPF.vm) file


Ensures that CustomFieldValue will be modified......



3. Entries in the atlassian-plugin.xml file.


<workflow-function key="ModifyCustomFieldPF" name="ModifyCustomFieldPF"   class="com.company.application.module.postfunction.ModifyCustomFieldPFImpl">
        <description>Description of your post function plug-in.</description>
        <function-class>"com.company.application.module.postfunction.ModifyCustomFieldPF</function-class>
        <orderable>true</orderable>
        <unique>true</unique>
        <deletable>true</deletable>
        <editable>true</editable>
        <resource type="velocity" name="view" location="templates/ModifyCustomFieldPF.vm"/>
    </workflow-function>      


4. Now build your plug-in and deploy the artifact and restart the application server.

Go to any workflow listed in Jira platform and check "Ensures that CustomFieldValue will be modified....." will be visible in the post function tab of the selected workflow transition.

Monday 16 July 2012

How to save Custom Field values?

Today, we will see, the ways to save values in Jira Custom Fields. Firstly, let us understand what is Custom Field in Jira platform.

Custom Fields is the additional feature given by the Jira platform to tailor Jira's platform for your organization needs. There are nearly 20 types of Custom Field Types available in Jira platform and saving values in custom fields depends on custom field's type.

Ex:-1
Let's suppose, there is a requirement to save a "Free Text" type custom field of name "Description", then

"Description" is the custom field name
"ABCD....XYZ" is the description value to be stored.


CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
CustomField customField =  getCustomFieldObjectByName("Description");
issue.setCustomFieldValue(customField, "ABCD....XYZ");


Ex:- 2
Let's suppose, there is a requirement to save a "Select Type" custom field type.

"Severity" is the custom field name and has options as "Critical", "High", "Medium" and "Low"
"High" is the custom field value to be stored.


public void updateCustomFieldValue(MutableIssue issue, String customFieldName, Object customFieldValue) {

CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
CustomField customField =  getCustomFieldObjectByName("Description");
String customFieldType = customField.getCustomFieldType().getName();
if (customFieldType.equalsIgnoreCase("Select Type")) {
List<Option> options = getOptionsManager().findByOptionValue((String)customFieldValue);
            if (options == null || options.isEmpty()) {
                logger.error("Can't set custom field value to " + customFieldValue + " because it doesn't correspond to a valid custom field option of the custom field : " + customField.getName());
            } else {
                issue.setCustomFieldValue(customField, options.get(0));
            }
}

}

private OptionsManager getOptionsManager() {
        return  ((OptionsManager) ComponentManager.getInstance().getComponentInstanceOfType(OptionsManager.class));
    }


Above mentioned scenario is valid for custom field types such as "Multi Checkboxes" and "Select Type"

Ex:- 3
Let's suppose, there is a requirement to save a "Cascading Select" custom field type.

State/District are the cascading select drop down list, based on the selection of state value, districts drop-down will be populated.

State/District is the custom field name;


/**
 * This method is to populate custom field value from "Cascading Select" type custom fields.
 * 
 **/
public static Map<String, String> populateStateDistrictMap(
            MutableIssue issue) {
        Map<String, String> stateDistrictmap = new HashMap<String, String>();
        CustomField stateDistrictCF=  getStateDistrictCF();

        Object stateDistrictCFValue= issue
                .getCustomFieldValue(stateDistrictCF);
        CustomFieldParams params = new CustomFieldParamsImpl(stateDistrictCF, stateDistrictCFValue);
        String stateCFValue = null;
        String districtCFValue = null;
        if (params != null && !params.isEmpty()) {
            stateCFValue = params.getFirstValueForNullKey().toString();
            districtCFValue = params.getFirstValueForKey("1").toString();
        }
        stateDistrictmap.put("STATE", stateCFValue);
        stateDistrictmap.put("DISTRICT", districtCFValue);

         return stateDistrictmap;
}


public static boolean updateStateDistrictMap(MutableIssue issue, String stateStr, String districtStr) {
        List<Option> stateCFOption = getOptionsManager().findByOptionValue(stateStr);
        List<Option> districtCFOption = getOptionsManager().findByOptionValue(districtStr);
        Map<String, Option> map = new HashMap<String, Option>();
        map.put((String) null, stateCFOption .get(0));
        map.put("1", districtCFOption .get(0));
        CustomField stateDistrictCF=  getStateDistrictCF(); 
        issueToCreate.setCustomFieldValue(stateDistrictCF, map);
}

private CustomField getStateDistrictCF(){
        CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
        CustomField stateDistrictCF=  getCustomFieldObjectByName("State/District"); 
        return  stateDistrictCF;
}

private OptionsManager getOptionsManager() {
        return  ((OptionsManager) ComponentManager.getInstance().getComponentInstanceOfType(OptionsManager.class));
    }






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>


Wednesday 15 February 2012

Exception after the implementation of Active Objects in Jira

2012-02-15 22:25:10,854 main ERROR      [sal.core.lifecycle.DefaultLifecycleManager] Error occurred while starting component 'com.company.application.ServiceImpl'. java.io.IOException: No such file or directory
java.lang.IllegalStateException: java.io.IOException: No such file or directory
        at net.java.ao.db.FileSystemUtils$1.get(FileSystemUtils.java:43)
        at net.java.ao.db.FileSystemUtils$1.get(FileSystemUtils.java:24)
        at com.google.common.base.Suppliers$MemoizingSupplier.get(Suppliers.java:93)
        at net.java.ao.db.FileSystemUtils.isCaseSensitive(FileSystemUtils.java:50)
        at net.java.ao.db.MySQLDatabaseProvider.isCaseSensetive(MySQLDatabaseProvider.java:143)
        at net.java.ao.schema.helper.DatabaseMetaDataReaderImpl.getTableNames(DatabaseMetaDataReaderImpl.java:46)
        at net.java.ao.schema.ddl.SchemaReader.readSchema(SchemaReader.java:73)
        at net.java.ao.schema.ddl.SchemaReader.readSchema(SchemaReader.java:61)
        at net.java.ao.schema.ddl.SchemaReader.readSchema(SchemaReader.java:52)
        at net.java.ao.schema.SchemaGenerator.generateImpl(SchemaGenerator.java:95)
        at net.java.ao.schema.SchemaGenerator.migrate(SchemaGenerator.java:68)
        at net.java.ao.EntityManager.migrate(EntityManager.java:143)
        at com.atlassian.activeobjects.internal.EntityManagedActiveObjects.migrate(EntityManagedActiveObjects.java:42)
        at com.atlassian.activeobjects.internal.AbstractActiveObjectsFactory.create(AbstractActiveObjectsFactory.java:52)
        at com.atlassian.activeobjects.internal.DelegatingActiveObjectsFactory.create(DelegatingActiveObjectsFactory.java:39)
        at com.atlassian.activeobjects.internal.RegistryBasedActiveObjectsProvider.get(RegistryBasedActiveObjectsProvider.java:29)
        at com.atlassian.activeobjects.osgi.DelegatingActiveObjects.getDelegate(DelegatingActiveObjects.java:126)
        at com.atlassian.activeobjects.osgi.DelegatingActiveObjects.migrate(DelegatingActiveObjects.java:31)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
        at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.doInvoke(ServiceInvoker.java:58)
        at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.invoke(ServiceInvoker.java:62)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:131)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:119)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invokeUnprivileged(ServiceTCCLInterceptor.java:56)
        at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invoke(ServiceTCCLInterceptor.java:39)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.osgi.service.importer.support.LocalBundleContextAdvice.invoke(LocalBundleContextAdvice.java:59)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:131)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:119)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        at $Proxy900.migrate(Unknown Source)


Possible reasons for above mentioned stack:
a. "temp" directory is not created in the apache-tomcat home directory.
b. Accessibility issue with "temp" directory of apache-tomcat home directory. Check the read-write accessibility permissions.

SQL query to retrieve users who logged into Jira for a given duration


SELECT
    user_name     AS UserName,
    display_name  AS FullName,
    email_address AS Email
FROM 
    cwd_user cu
LEFT JOIN 
    (SELECT
          user_id, attribute_value
     FROM
          cwd_user_attributes
     WHERE
          attribute_name = 'login.lastLoginMillis' 
    ) login
ON
    cu.id = login.user_id
WHERE
    lastLogin.previousLogininMillis BETWEEN ? AND ? ORDER BY user_name

NOTE: Valid for Jira 4.3.3 version and above