Possible race condition with script listener. Any advice

Hi guys,

I have a script listener that will create and link a ticket if a ticket priority equals 'P1', or is modified to 'P1' from another priority. It seems to work flawlessly most of the time. It's designed to not create/link a ticket if a link already exists. Anyway, I got a complaint that my listener created 2 tickets and linked them to the source ticket – which is not desired. When I look at the history of the source ticket, I see that one user changes the source ticket from p2 to p1 (which triggers the listener). At the same time I see another user add an attachment to the ticket. I'm assuming this is happening before the check is made for an existing link. Below is my script. Any tips on how to prevent this from happening? By the way – I don't want to lock the script from running if it is already running – it is conceivable that this could be modifying more than one ticket at once. If a lock is needed, I need a way to lock changes from an existing ticket that is already being modified.


Tips much appreciated.


import com.atlassian.jira.event.issue.AbstractIssueEventListener
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.customfields.option.Option

public class LINKAssignmentListener extends AbstractIssueEventListener {

    void workflowEvent(IssueEvent event) {
        // only one central way ...


    void customEvent(IssueEvent event) {

        // Setting up the various Managers
        def issueManager = ComponentAccessor.getIssueManager()       //for testing in console
        def projectManager = ComponentAccessor.getProjectManager()
        def customFieldManager = ComponentAccessor.getCustomFieldManager()
        def optionsManager = ComponentAccessor.getOptionsManager()
        def issueLinkManager = ComponentAccessor.getIssueLinkManager()

        def results = ""
//def myIssue = 'FOOBAR-28234'      //for testing in console

        def myNewIssue = ''

//MutableIssue issue = issueManager.getIssueObject(myIssue)        //for testing in console

        def issue = event.getIssue()

        def priority = issue.getPriorityObject().getName()

// first -- we only run if ticket is a p1

//if (priority == 'P1')                                              //for testing in console
        List changeItems = []
        try {
            changeItems = event.getChangeLog().getRelated("ChildChangeItem")

        catch (e) {

            results += "Skipping changeItems -- looks like issue created via workflow."


        if ((priority == 'P1') ||
                (priority == 'P1' && changeItems.any {
                    it.get('field') == 'priority'
                })) {

            // Next -- we check if a link to a LINK ticket already exists, if one does, we stop script.

            String linkResult = '';
            Collection<IssueLink> olinks = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId());
            Collection<IssueLink> ilinks = ComponentAccessor.getIssueLinkManager().getInwardLinks(issue.getId());
            Collection<IssueLink> links = olinks + ilinks;

            for (IssueLink il : links) {
                linkResult += il.getSourceObject().getProjectObject().getKey()
                linkResult += il.getDestinationObject().getProjectObject().getKey()
            if (linkResult.contains("LINK")) {

                results += "Link already exists. I shall stop now. "          //for testing in console
            } else {

                // Let's create a LINK ticket
                // .... but first we need to copy existing custom field values from the FOOBAR ticket

                // Classification field.
                CustomField classification = customFieldManager.getCustomFieldObjects(issue).find { it.name == "Classification" }

                def region = issue.getCustomFieldValue(classification)[0].toString()
                def customer = issue.getCustomFieldValue(classification)[1].toString()
                def country = issue.getCustomFieldValue(classification)[2].toString()

                //In order to set the above Classification field values in the target LINK ticket, I need to get the Option Ids that correspond to them.

                Long regionId = null
                Long customerId = null
                Long countryId = null

                def cfConfig = classification.getRelevantConfig(issue)

                def classificationOptions = optionsManager.getOptions(cfConfig)

                for (Option regionOption : classificationOptions) {
                    if (regionOption.getValue().equals(region)) {
                        regionId = regionOption.getOptionId()
                        def customers = regionOption.getChildOptions()
                        for (Option customerOption : customers) {
                            if (customerOption.getValue().equals(customer)) {
                                customerId = customerOption.getOptionId()
                                def countries = customerOption.getChildOptions()
                                for (Option countryOption : countries) {
                                    if (countryOption.getValue().equals(country)) {
                                        countryId = countryOption.getOptionId()

                // We have the needed data from the original FOOBAR ticket. Now we can create the LINK ticket:

                def projectName = "Linked Ticket Example" //Target project name

                def project = projectManager.getProjectObjByName(projectName)

                //  String currentUser = event.getUser().getName();
                User currentUserObj = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

                // This is used for creating the new LINK ticket
                def issueService = ComponentAccessor.getIssueService()

                def issueInputParameters = issueService.newIssueInputParameters();

// Setting System Fields
                issueInputParameters.setIssueTypeId('34')  // 'Problem' issue type in LINK

// Setting Custom Fields

                // First we'll do the  classification field values, since we have that already defined from above.

                issueInputParameters.addCustomFieldValue(classification.getId() + ":0", regionId.toString())
                issueInputParameters.addCustomFieldValue(classification.getId() + ":1", customerId.toString())
                issueInputParameters.addCustomFieldValue(classification.getId() + ":2", countryId.toString())

                def customField = customFieldManager.getCustomFieldObject("customfield_11135") // Support Manager
                issueInputParameters.addCustomFieldValue(customField.getId(), 'support-guy')    // 'support-guy' option for Support Manager

                customField = customFieldManager.getCustomFieldObject("customfield_10891") // Action Required From
                issueInputParameters.addCustomFieldValue(customField.getId(), '13118')    // 'Foo Bar' option for Action Required From

                customField = customFieldManager.getCustomFieldObject("customfield_11124") // Director
                issueInputParameters.addCustomFieldValue(customField.getId(), '12902')    // 'Director Guy' option for Director

                customField = customFieldManager.getCustomFieldObject("customfield_13190") // LL Needed?
                issueInputParameters.addCustomFieldValue(customField.getId(), '16741')    // 'No' option for LL Needed?

                customField = customFieldManager.getCustomFieldObject("customfield_11812") // Is there a high probability this will happen again?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13920')   // 'Yes' option for  Is there a high probability this will happen again?

                customField = customFieldManager.getCustomFieldObject("customfield_11815") // Does the Customer perceive this as urgent?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13926')   // 'Yes' option for  Does the Customer perceive this as urgent?

                customField = customFieldManager.getCustomFieldObject("customfield_11816") // Does it create an outage?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13928')   // 'Yes' option for  Does it create an outage?

                customField = customFieldManager.getCustomFieldObject("customfield_11820") // Revenue Loss?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13936')   // 'Yes' option for  Revenue Loss?

                customField = customFieldManager.getCustomFieldObject("customfield_11813") // Are there any timeline commitments / dependencies?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13923')   // 'No' option for  Are there any timeline commitments / dependencies?

                customField = customFieldManager.getCustomFieldObject("customfield_11814") // Are there any SLA dependencies?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13925')   // 'No' option for  Are there any SLA dependencies?

                customField = customFieldManager.getCustomFieldObject("customfield_11819") // Does this problem create an additional ongoing workload?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13935')   // 'No' option for  Does this problem create an additional ongoing workload?

                customField = customFieldManager.getCustomFieldObject("customfield_11817") // Are either ALL users of one customer affected or ALL customers affected?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13931')   // 'No' option for  Are either ALL users of one customer affected or ALL customers affected?

                customField = customFieldManager.getCustomFieldObject("customfield_11818") // Would there be penalties?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13932')   // 'Yes' option for  Would there be penalties?

                // Let's create the new LINK tiket

                def newIssueResult = issueService.validateCreate(currentUserObj, issueInputParameters);
                if (!newIssueResult.isValid()) {
                    results += "could not create target issue: " + newIssueResult.getErrorCollection()

                def newIssue = issueService.create(currentUserObj, newIssueResult).getIssue()

                results += "Created issue ${newIssue.key} ."

                Long linkType = 10196  // 'Relates to' link type

                try {
                    issueLinkManager.createIssueLink(issue.getId(), newIssue.getId(), linkType, Long.valueOf(1), currentUserObj)
                    results += "Added link from  ${issue.key} of type \'Relates\'  to ${newIssue.key}"

                catch (e) {

                    results += "PROBLEM adding link from ${issue.key} of type ${linkType} to ${newIssue.key} :: ${e}"



        } else {

            results += "Ticket is not P1"

//return results -- for testing




1 answer

We have a similar issue

We run a bulk transition on a set of issues which trigger an event which are catched by a script listener which generates a custom mail ...

What we have now is that some mails have the subject of the previous issue, which is strange to say the least.


JIRA 6.2.7
Script Listener 3.0.16

cc: @Jamie Echlin (Adaptavist) 

Suggest an answer

Log in or Sign up to answer
Community showcase
Published Mar 13, 2019 in Marketplace Apps

Marketplace Spotlight: Marketing apps for Confluence to keep your teams working on the same page


280 views 0 7
Read article

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you