import com.atlassian.jira.bc.issue.link.IssueLinkService.AddIssueLinkValidationResult import com.atlassian.jira.bc.issue.link.RemoteIssueLinkService.CreateValidationResult import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.link.IssueLink import com.atlassian.jira.user.ApplicationUser import com.exalate.api.domain.INonPersistentReplica import com.exalate.api.domain.twintrace.ITwinTrace import com.exalate.api.persistence.twintrace.ITwinTraceRepository import com.exalate.basic.domain.hubobject.v1.BasicHubIssue import com.exalate.basic.domain.hubobject.v1.BasicHubIssueLink import com.exalate.basic.domain.hubobject.v1.BasicHubIssueLink.IssueLinkType import com.exalate.node.hubobject.v1_3.NodeHelper import com.exalate.api.domain.connection.IConnection import com.exalate.api.domain.request.ISyncRequest import com.exalate.services.utils.FutureUtils import com.exalate.basic.domain.BasicIssueKey import org.slf4j.Logger import scala.collection.immutable.Seq /** Usage: Add the snippet below to the end of your "Outgoing sync": IssueLinks.send() -------------------------------- Add the snippet below to the end of your "Incoming sync": IssueLinks.receive( linkTypeMapping = [ "Blocks" : "Relates" ] ) -------------------------------- * */ class IssueLinks { static LOG = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.IssueLinks") static Closure errorOnNoLinkType = { Map args -> BasicHubIssueLink remoteLink = args."remoteLink" Long localOtherIssueIdFromRemoteLink = args.localOtherIssueIdFromRemoteLink Boolean isOutward = args.isOutward IConnection connection = args.connection throw new com.exalate.api.exception.IssueTrackerException(""" Could not find link name for remote link type `${remoteLink.linkTypeName}` and link `${remoteLink.linkName}`. Please check your configuration for Connection `${ connection.name}` """.toString()) } as Closure; static Closure logOnNoLinkType = { Map args -> BasicHubIssueLink remoteLink = args."remoteLink" Long localOtherIssueIdFromRemoteLink = args.localOtherIssueIdFromRemoteLink Boolean isOutward = args.isOutward IConnection connection = args.connection LOG.error(""" Could not find link name for remote link type `${remoteLink.linkTypeName}` and link `${remoteLink.linkName}`. Skipping it. """.toString()) return null } static BasicHubIssue send() { def context = null if (com.exalate.processor.jira.JiraCreateReplicaProcessor.dataFilterContext != null && com.exalate.processor.jira.JiraCreateReplicaProcessor.dataFilterContext.get() == true) { context = com.exalate.processor.jira.JiraCreateReplicaProcessor.threadLocalContext.get() } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script Epic.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue replica = context.replica as BasicHubIssue final BasicHubIssue issue = context.issue as BasicHubIssue final IConnection connection = context.connection send(replica, issue, connection) } static BasicHubIssue send(BasicHubIssue replica, BasicHubIssue issue, IConnection connection) { // def ilserv = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.link.IssueLinkService.class) // def ilm = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.issue.link.IssueLinkManager.class) // def iltm = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.issue.link.IssueLinkTypeManager.class) def rilserv = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.link.RemoteIssueLinkService.class) def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() def proxyUser = getProxyUser() def jIssue = im.getIssueObject(issue.id as Long) // def inwardIssueLinks = ilm.getInwardLinks(issue.id as Long) // def outwardIssueLinks = ilm.getOutwardLinks(issue.id as Long) // def issueLinksForIssue = inwardIssueLinks + outwardIssueLinks def urlLinksForIssue = jIssue ? rilserv.getRemoteIssueLinksForIssue(proxyUser, jIssue)?.remoteIssueLinks ?: [] : [] replica.issueLinks = issue.issueLinks replica.customKeys."IssueLinksContext" = [ "issueLinkMappings" : [], "remoteLinkMappings" : [], "issueLinks" : issue.issueLinks.findAll { l -> l.issueLinkType == BasicHubIssueLink.IssueLinkType.ISSUE.name() }, "remoteLinks" : issue.issueLinks .findAll { l -> l.issueLinkType == BasicHubIssueLink.IssueLinkType.REMOTE_URL.name() } .collect { link -> def theJLink = urlLinksForIssue.find { jLink -> jLink.summary == link.linkName && jLink.url == jLink.url } if(theJLink == null) return null else return [ "id" : theJLink.id as String, "linkName" : theJLink.summary as String, "url" : theJLink.url as String, ] } .findAll(), ] replica } static void receive() { receive(defaultLinkMappingFnFn([:])) } static void receive(BasicHubIssue replica, BasicHubIssue issue, IConnection connection, NodeHelper nodeHelper, ISyncRequest syncRequest) { receive([:], replica, issue, connection, nodeHelper, errorOnNoLinkType, syncRequest) } static void receive(Map linkTypeMapping) { receive( linkTypeMapping, errorOnNoLinkType ) } static void receive(Map linkTypeMapping, Closure onLinkTypeNotFoundFn) { def context = null if (com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraCreateIssueProcessor.threadLocalContext.get() } else if (com.exalate.processor.jira.JiraChangeIssueProcessor.changeProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraChangeIssueProcessor.threadLocalContext.get() } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script IssueLinks.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue replica = context.replica final BasicHubIssue issue = context.issue final NodeHelper nodeHelper = context.nodeHelper final IConnection connection = context.connection final ISyncRequest syncRequest = context.syncRequest receive(replica, issue, connection, nodeHelper, defaultLinkMappingFnFn(linkTypeMapping), onLinkTypeNotFoundFn, syncRequest) } static void receive(Map linkTypeMapping, BasicHubIssue replica, BasicHubIssue issue, IConnection connection, NodeHelper nodeHelper, Closure onLinkTypeNotFoundFn, ISyncRequest syncRequest) { receive(replica, issue, connection, nodeHelper, defaultLinkMappingFnFn(linkTypeMapping), onLinkTypeNotFoundFn, syncRequest) } static def defaultLinkMappingFnFn = { Map linkTypeMapping -> def iltm = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.issue.link.IssueLinkTypeManager.class) return { BasicHubIssueLink remoteLink -> def desiredLinkType = (linkTypeMapping[remoteLink.linkTypeName] ?: remoteLink.linkTypeName) def ilt = iltm.getIssueLinkTypesByName(desiredLinkType).find() if (ilt == null) { return null } return [ "issueLinkTypeName" : ilt.name, ] } } static void receive(Closure linkMappingFn) { def context = null if (com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraCreateIssueProcessor.threadLocalContext.get() } else if (com.exalate.processor.jira.JiraChangeIssueProcessor.changeProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraChangeIssueProcessor.threadLocalContext.get() } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script Epic.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue replica = context.replica final BasicHubIssue issue = context.issue final NodeHelper nodeHelper = context.nodeHelper final IConnection connection = context.connection final ISyncRequest syncRequest = context.syncRequest receive(replica, issue, connection, nodeHelper, linkMappingFn, errorOnNoLinkType, syncRequest) } static void receive(BasicHubIssue replica, BasicHubIssue issue, IConnection connection, NodeHelper nodeHelper, Closure linkMappingFn, Closure onLinkTypeNotFoundFn, ISyncRequest syncRequest) { def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() def ilserv = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.link.IssueLinkService.class) def jiraHome = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.config.util.JiraHome.class) def rilserv = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.link.RemoteIssueLinkService.class) def jIssue = null def iltm = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.issue.link.IssueLinkTypeManager.class) def context = null def remoteReplica = null if (com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraCreateIssueProcessor.threadLocalContext.get() remoteReplica = context.remoteReplica } else if (com.exalate.processor.jira.JiraChangeIssueProcessor.changeProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraChangeIssueProcessor.threadLocalContext.get() remoteReplica = context.currentReplica } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script Epic.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue issueBeforeScript = context.issueBeforeScript as BasicHubIssue final List traces = context.traces final List blobMetadataList = context.blobMetadataList def proxyUser = getProxyUser() def toLocalIssueLink = { BasicHubIssueLink remoteLink, Long localOtherIssueIdFromRemoteLink -> def params = linkMappingFn(remoteLink) def issueLinkTypeName = params?."issueLinkTypeName" as String if (params == null || params."issueLinkTypeName" == null) { issueLinkTypeName = onLinkTypeNotFoundFn( [ "remoteLink":remoteLink, "localOtherIssueIdFromRemoteLink":localOtherIssueIdFromRemoteLink, "connection":connection ] ) } if (issueLinkTypeName == null) { // return null which is going to be skipped if no link type returned return null } def ilts = iltm.getIssueLinkTypes() def localLinkType = ilts.find { issueLinkTypeName.equalsIgnoreCase(it.name) } if (localLinkType == null) { context.debug("Couldn't find issue link type `$issueLinkTypeName` among available link types: `${ilts?.collect { it.name }?.join("`,`")}`") } def issueLinkName = remoteLink.isOutward ? localLinkType.outward : localLinkType.inward // new BasicHubIssueLink( //// this.otherIssueId = var1; // localOtherIssueIdFromRemoteLink, //// this.linkName = var2; // issueLinkName, //// this.linkTypeName = var3; // issueLinkTypeName, //// this.linkType = var4; // BasicHubIssueLink.IssueLinkType.ISSUE, //// this.url = var5; // null // ) ExalateApi.issueLink( localOtherIssueIdFromRemoteLink as Long, issueLinkName, issueLinkTypeName, remoteLink.isOutward ) } def toLocalUrlLink = { BasicHubIssueLink remoteLink -> new BasicHubIssueLink( null, remoteLink.linkName, remoteLink.linkTypeName, BasicHubIssueLink.IssueLinkType.REMOTE_URL, remoteLink.url ) } def validateAddIssueLinks = { ApplicationUser u, String linkName, Long linkTypeId, direction, String otherIssueKey -> try { //Jira 7 ilserv.validateAddIssueLinks(u, jIssue, linkTypeId, direction, [otherIssueKey], true) } catch (Exception ignore) { throw ignore // Jira 6 //noinspection GroovyAssignabilityCheck ilserv.validateAddIssueLinks(u.directoryUser, jIssue, linkName, [otherIssueKey]) } } def addIssueLinks = { ApplicationUser u, AddIssueLinkValidationResult validationResult -> try { //Jira 7 ilserv.addIssueLinks(u, validationResult) } catch (Exception ignore) { // Jira 6 //noinspection GroovyAssignabilityCheck ilserv.addIssueLinks(u.directoryUser, validationResult) } } def validateCreateRemoteLink = { ApplicationUser u, BasicHubIssueLink remoteLink -> try { //Jira 7 rilserv.validateCreate( u, new com.atlassian.jira.issue.link.RemoteIssueLinkBuilder() .issueId(jIssue.id) .summary(remoteLink.linkName) .url(remoteLink.url) .build() ) } catch (Exception ignore) { // Jira 6 //noinspection GroovyAssignabilityCheck rilserv.validateCreate( u.directoryUser, new com.atlassian.jira.issue.link.RemoteIssueLinkBuilder() .issueId(jIssue.id) .summary(remoteLink.linkName) .url(remoteLink.url) .build() ) } } def createRemoteLink = { ApplicationUser u, CreateValidationResult result -> try { //Jira 7 rilserv.create(u, result) } catch (Exception ignore) { // Jira 6 //noinspection GroovyAssignabilityCheck rilserv.create(u.directoryUser, result) } } def linksAreEqual = { Boolean isLocalOutward, IssueLink l, rl -> /* replica.customKeys."IssueLinksContext" = [ ... "issueLinks" : ... .collect { Map bothLinks -> ... return [ "id" : jLink.id as String, "linkTypeName" : jLink.issueLinkType.name as String, "otherIssueId" : otherIssueId as String, "otherIssueKey" : otherIssue.key, "isOutward" : isOutward, "linkTypeStyle" : jLink.issueLinkType.style ] }, "remoteLinks" : [...], ] */ def localIssueId = ExalateApi.getLocalIssueFromRemoteId(rl.otherIssueId as Long, nodeHelper)?.id def params = linkMappingFn(ExalateApi.issueLink(rl.otherIssueId as Long, rl.linkName as String, rl.linkTypeName as String, rl.isOutward)) def desiredLinkTypeName = params?.issueLinkTypeName if (!params || !(desiredLinkTypeName instanceof String)) { desiredLinkTypeName = onLinkTypeNotFoundFn( [ "remoteLink":ExalateApi.issueLink(rl.otherIssueId as Long, rl.linkName as String, rl.linkTypeName as String, rl.isOutward), "localOtherIssueIdFromRemoteLink":localIssueId as Long, "isOutward":rl."isOutward" as Boolean, "connection":connection ] ) } if (desiredLinkTypeName == null) { return null } if (!localIssueId) { return false } def currentOtherIssueId = isLocalOutward ? l.destinationId : l.sourceId if ((rl.isOutward as Boolean) == isLocalOutward && desiredLinkTypeName == l.issueLinkType.name && localIssueId != null && localIssueId as String == (currentOtherIssueId as String)) { return true } // LOG.debug("rl and l are different") // LOG.debug("rl.isOutward: " + rl.isOutward + " vs isLocalOutward:"+isLocalOutward) // LOG.debug("desiredLinkTypeName: " + desiredLinkTypeName + " vs l.issueLinkType.name: "+l.issueLinkType.name) // LOG.debug("localIssueId: " + localIssueId + " vs currentOtherIssueId: "+currentOtherIssueId) return false } /** * check which links are present in locally (and have otherIssue under_sync), but are not present in the remote link context */ def calculateLinksToBeRemoved = { def ilm = ComponentAccessor.issueLinkManager def outLinks = ilm.getOutwardLinks(issue.id as Long).findAll { l -> ExalateApi.isUnderSync(l.destinationId as String, nodeHelper) } def inLinks = ilm.getInwardLinks(issue.id as Long).findAll { l -> ExalateApi.isUnderSync(l.sourceId as String, nodeHelper) } def remoteIssueLinkContext = replica.customKeys."IssueLinksContext"?."issueLinks" as List> ?: replica.issueLinks; def localOutLinksNotPresentOnRemote = outLinks.findAll { l -> if (l.issueLinkType.subTaskLinkType || l.issueLinkType.systemLinkType) { return false; // don't remove system links and sub-task links } def foundIt = remoteIssueLinkContext.any { rl -> def eq = linksAreEqual(true, l, rl); eq != null && eq } !foundIt } def localInLinksNotPresentOnRemote = inLinks.findAll { l -> if (l.issueLinkType.subTaskLinkType || l.issueLinkType.systemLinkType) { return false; // don't remove system links and sub-task links } def foundIt = remoteIssueLinkContext.any { rl -> def eq = linksAreEqual(false, l, rl); eq != null && eq } !foundIt } List result = [] result.addAll(localOutLinksNotPresentOnRemote) result.addAll(localInLinksNotPresentOnRemote) result } /** *check which links are present in remote context, but are not present locally (while otherIssue is under_sync) * */ def calculateLinksToBeAdded = { def ilm = ComponentAccessor.issueLinkManager def outLinks = ilm.getOutwardLinks(issue.id as Long).findAll { l -> ExalateApi.isUnderSync(l.destinationId as String, nodeHelper) } def inLinks = ilm.getInwardLinks(issue.id as Long).findAll { l -> ExalateApi.isUnderSync(l.sourceId as String, nodeHelper) } def remoteIssueLinkContext = replica.customKeys."IssueLinksContext"?."issueLinks" as List> ?: replica.issueLinks; def linksNotPresentInRemote = remoteIssueLinkContext.findAll { rl -> def localIssueId = ExalateApi.getLocalIssueFromRemoteId(rl.otherIssueId as Long, nodeHelper)?.id if (!localIssueId) { return false } def foundIt = outLinks.any { l -> def eq = linksAreEqual(rl.isOutward, l, rl); eq != null && eq } !foundIt } linksNotPresentInRemote } if (!replica.customKeys."IssueLinksContext" && !replica.issueLinks) { return } BasicHubIssue replicaBhi = replica as BasicHubIssue BasicHubIssue issueBhi = issue as BasicHubIssue def remoteReplicaBhi = remoteReplica as INonPersistentReplica if(issue.id != null){ jIssue = im.getIssueObject(issue.id as Long) // calculate links-to-be-removed: check which links are present in locally (and have otherIssue under_sync), but are not present in the remote link context def linksToBeRemoved = calculateLinksToBeRemoved() def _i = im.getIssueObject(issue.id as Long) linksToBeRemoved.each { l -> LogIn.tryLogInFinallyLogOut { def vr = ilserv.validateDelete(proxyUser, _i, l) if (!vr.valid) { def isOutward = l.sourceId == (issue.id as Long) def linkName = isOutward ? l.issueLinkType.outward : l.issueLinkType.inward def errorMessagesStr = vr.errorCollection.errorMessages.join("; ") def errorsStr =vr.errorCollection.errors.collect { k, v -> "$k - $v".toString() }.join(";") def reasonsStr =vr.errorCollection.reasons.collect { it.name() }.join(";") throw new com.exalate.api.exception.IssueTrackerException(""" Unable to delete link `${l.sourceObject.key}` $linkName `${l.destinationObject.key}` (${l.id}). Error messages: `$errorMessagesStr` Errors: `$errorsStr` Reasons: `$reasonsStr` """.toString()) } ilserv.delete(vr) } } // calculate links-to-be-added: check which links are present in remote context, but are not present locally (while otherIssue is under_sync) def linksToBeAdded = calculateLinksToBeAdded() linksToBeAdded.each { rl -> def localIssue = ExalateApi.getLocalIssueFromRemoteId(rl.otherIssueId as Long, nodeHelper) def link = toLocalIssueLink( ExalateApi.issueLink(rl.otherIssueId as Long, rl.linkName as String, rl.linkTypeName as String, rl.isOutward), localIssue.id as Long ) if (link == null) { // skip it return } def localOtherIssueKey = localIssue.key as String def linkTypeId = ilserv.getIssueLinkTypes().find{it.getName() == link.getLinkTypeName()}?.id def direction = link.isOutward? com.atlassian.jira.issue.link.Direction.OUT : com.atlassian.jira.issue.link.Direction.IN com.atlassian.jira.bc.issue.link.IssueLinkService.AddIssueLinkValidationResult addIssueLinkValidationResult = validateAddIssueLinks(proxyUser, link.linkName, linkTypeId, direction, localOtherIssueKey) if (addIssueLinkValidationResult.getErrorCollection().hasAnyErrors()) { throw new com.exalate.api.exception.IssueTrackerException("Failed to create a link from `${issue.key}` to `${localOtherIssueKey}`. " + com.exalate.util.ErrorCollectionUtils.getFormattedErrorMessage(addIssueLinkValidationResult.getErrorCollection())) } addIssueLinks(proxyUser, addIssueLinkValidationResult) } } else { CreateIssue.create( replicaBhi, issueBhi, syncRequest, nodeHelper, issueBeforeScript, remoteReplicaBhi, traces, blobMetadataList) { jIssue = im.getIssueObject(issue.id as Long) // calculate links-to-be-removed: check which links are present in locally (and have otherIssue under_sync), but are not present in the remote link context def linksToBeRemoved = calculateLinksToBeRemoved() def _i = im.getIssueObject(issue.id as Long) linksToBeRemoved.each { l -> LogIn.tryLogInFinallyLogOut { def vr = ilserv.validateDelete(proxyUser, _i, l) if (!vr.valid) { def isOutward = l.sourceId == (issue.id as Long) def linkName = isOutward ? l.issueLinkType.outward : l.issueLinkType.inward def errorMessagesStr = vr.errorCollection.errorMessages.join("; ") def errorsStr =vr.errorCollection.errors.collect { k, v -> "$k - $v".toString() }.join(";") def reasonsStr =vr.errorCollection.reasons.collect { it.name() }.join(";") throw new com.exalate.api.exception.IssueTrackerException(""" Unable to delete link `${l.sourceObject.key}` $linkName `${l.destinationObject.key}` (${l.id}). Error messages: `$errorMessagesStr` Errors: `$errorsStr` Reasons: `$reasonsStr` """.toString()) } ilserv.delete(vr) } } // calculate links-to-be-added: check which links are present in remote context, but are not present locally (while otherIssue is under_sync) def linksToBeAdded = calculateLinksToBeAdded() linksToBeAdded.each { rl -> def localIssue = ExalateApi.getLocalIssueFromRemoteId(rl.otherIssueId as Long, nodeHelper) def link = toLocalIssueLink( ExalateApi.issueLink(rl.otherIssueId as Long, rl.linkName as String, rl.linkTypeName as String, rl.isOutward), localIssue.id as Long ) if (link == null) { // skip it return } def localOtherIssueKey = localIssue.key as String def linkTypeId = ilserv.getIssueLinkTypes().find{it.getName() == link.getLinkTypeName()}.id def direction = link.isOutward? com.atlassian.jira.issue.link.Direction.OUT : com.atlassian.jira.issue.link.Direction.IN com.atlassian.jira.bc.issue.link.IssueLinkService.AddIssueLinkValidationResult addIssueLinkValidationResult = validateAddIssueLinks(proxyUser, link.linkName, linkTypeId, direction, localOtherIssueKey) if (addIssueLinkValidationResult.getErrorCollection().hasAnyErrors()) { throw new com.exalate.api.exception.IssueTrackerException("Failed to create a link from `${issue.key}` to `${localOtherIssueKey}`. " + com.exalate.util.ErrorCollectionUtils.getFormattedErrorMessage(addIssueLinkValidationResult.getErrorCollection())) } addIssueLinks(proxyUser, addIssueLinkValidationResult) } return null } } } private static ApplicationUser getProxyUser() { def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) def proxyUser = nserv.getProxyUser() proxyUser } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~EXALATE API EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static class ExalateApi { static log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.IssueLinks") static IConnection getConnection(int id) { try { // Exalate 4.2.X def relrepo = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.persistence.relation.IRelationRepository.class) relrepo.getRelation(id) } catch (MissingMethodException ignore) { // Exalate 4.3.X def relrepo = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.persistence.relation.IRelationRepository.class) relrepo.getConnectionById(id) } } static IConnection getConnection(Integer id) { if (id == null) { return null } getConnection(id.intValue()) } static Map getLocalIssueFromRemoteId(Long remoteIssueId, NodeHelper nodeHelper) { if (remoteIssueId == null) { return null } def longRemoteIssueId = remoteIssueId as Long def tt = nodeHelper.twinTraceRepository.getTwinTraceByRemoteIssueId(nodeHelper.connection, longRemoteIssueId as String) if(tt == null) return null def issueKey = tt.localReplica.issueKey; def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() if(im.getIssueObject(issueKey.id as Long) == null) return null return ["id": issueKey.id, "urn": issueKey.urn, "key": issueKey.urn] } static boolean isUnderSync(String localIssueIdStr, NodeHelper nodeHelper) { if (localIssueIdStr == null) { return null } def localIssueKey = new BasicIssueKey(localIssueIdStr, localIssueIdStr) nodeHelper.twinTraceRepository.getTwinTraceByLocalIssueKey(nodeHelper.connection, localIssueKey) != null } static com.atlassian.jira.user.ApplicationUser getProxyUser() { def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) nserv.proxyUser } static BasicHubIssueLink issueLink(Long otherIssueId, String linkName, String linkTypeName, Boolean isOutward) { try { new BasicHubIssueLink(otherIssueId, linkName, linkTypeName, IssueLinkType.ISSUE, null, isOutward) } catch (Exception ignore) { new BasicHubIssueLink(otherIssueId, linkName, linkTypeName, IssueLinkType.ISSUE, null) } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~CREATE ISSUE EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static class CreateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.IssueLinks") private static def doCreate = { com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, com.exalate.api.domain.INonPersistentReplica remoteReplica, List traces, List blobMetadataList, Logger log -> def firstSync = com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true def issueLevelError = { String msg -> new com.exalate.api.exception.IssueTrackerException(msg) } def issueLevelError2 = { String msg, Throwable c -> new com.exalate.api.exception.IssueTrackerException(msg, c) } def toExIssueKey = { com.atlassian.jira.issue.MutableIssue i -> new com.exalate.basic.domain.BasicIssueKey(i.id, i.key) } final def authCtxInternal = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() final def imInternal = com.atlassian.jira.component.ComponentAccessor.issueManager final def umInternal = com.atlassian.jira.component.ComponentAccessor.userManager final def nservInternal = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) final def hohfInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.hubobject.IHubObjectHelperFactory.class) //noinspection GroovyAssignabilityCheck final def hohInternal2 = hohfInternal2.get(remoteReplica.payload.version) if (issue.id != null) { def existingIssue = imInternal.getIssueObject(issue.id as Long) if (existingIssue != null) { return [existingIssue, toExIssueKey(existingIssue)] } } def proxyAppUserInternal = nservInternal.getProxyUser() def loggedInUser = authCtxInternal.getLoggedInUser() log.debug("Logged user is " + loggedInUser) def reporterAppUser = null if (issue.reporter != null) { reporterAppUser = umInternal.getUserByKey(issue.reporter?.key) } reporterAppUser = reporterAppUser ?: proxyAppUserInternal issue.project = issue.project ?: ({ nodeHelper.getProject(issue.projectKey) })() issue.type = issue.type ?: ({ nodeHelper.getIssueType(issue.typeName) })() def jIssueInternal = null try { LogIn.logIn(reporterAppUser) if (issue.id != null) { def existingIssue = imInternal.getIssueObject(issue.id as Long) if (existingIssue != null) { issue.id = existingIssue.id issue.key = existingIssue.key return [existingIssue, toExIssueKey(existingIssue)] } } def cir = null try{ cir = hohInternal2.createNodeIssueWith(issue, hohInternal2.createHubIssueTemplate(), null, [:], blobMetadataList, syncRequest) } catch (MissingMethodException e){ cir = hohInternal2.createNodeIssueWith(issue, hohInternal2.createHubIssueTemplate(), null, [:], blobMetadataList, syncRequest.getConnection()) } def createdIssueKey = cir.getIssueKey(); jIssueInternal = imInternal.getIssueObject(createdIssueKey.id) if (issue.id != null) { def oldIssueKey = jIssueInternal.key def oldIssueId = jIssueInternal.id try { jIssueInternal.key = issue.key jIssueInternal.store() } catch (Exception e) { log.error("""Failed to sync issue key: ${e.message}. Please contact Exalate Support. Deleting issue `$oldIssueKey` ($oldIssueId)""".toString(), e) imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } } issue.id = jIssueInternal.id issue.key = jIssueInternal.key return [jIssueInternal, toExIssueKey(jIssueInternal)] } catch (com.exalate.api.exception.TransitionException te) { if (firstSync && te.issueKey && te.issueKey.id) { def jIssueInternal2 = imInternal.getIssueObject(te.issueKey.id) imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal2 as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw te } catch (com.exalate.api.exception.IssueTrackerException ite) { if (firstSync && jIssueInternal != null) { imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw ite } catch (Exception e) { if (firstSync && jIssueInternal != null) { imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw issueLevelError2("""Failed to create issue: ${ e.message }. Please review the script or contact Exalate Support""".toString(), e) } finally { LogIn.logIn(loggedInUser) } } /** * @param whenIssueCreatedFn - a callback closure executed after the issue has been created * */ static com.exalate.basic.domain.BasicIssueKey create( com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, com.exalate.api.domain.INonPersistentReplica remoteReplica, List traces, List blobMetadataList, Closure whenIssueCreatedFn) { def firstSync = com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true def (_jIssue, _exIssueKey) = doCreate(replica, issue, syncRequest, nodeHelper, issueBeforeScript, remoteReplica, traces, blobMetadataList, log) com.atlassian.jira.issue.MutableIssue jIssue = _jIssue as com.atlassian.jira.issue.MutableIssue com.exalate.basic.domain.BasicIssueKey exIssueKey = _exIssueKey as com.exalate.basic.domain.BasicIssueKey try { whenIssueCreatedFn() UpdateIssue.update(replica, issue, syncRequest, nodeHelper, issueBeforeScript, traces, blobMetadataList, jIssue, exIssueKey) } catch (Exception e3) { final def imInternal = com.atlassian.jira.component.ComponentAccessor.issueManager final def nservInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) def proxyAppUserInternal = nservInternal2.getProxyUser() if (firstSync && _jIssue != null) { imInternal.deleteIssue(proxyAppUserInternal, _jIssue as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw e3 } return exIssueKey } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~UPDATE ISSUE EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static class UpdateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.IssueLinks") private static def doUpdate = { com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, List traces, List blobMetadataList, com.atlassian.jira.issue.MutableIssue jIssue, com.exalate.basic.domain.BasicIssueKey exIssueKey -> try { final def hohfInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.hubobject.IHubObjectHelperFactory.class) //noinspection GroovyAssignabilityCheck final def hohInternal2 = hohfInternal2.get("1.2.0") final def nservInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) def proxyAppUserInternal2 = nservInternal2.getProxyUser() log.info("performing the update for the issue `" + jIssue.key + "` for remote issue `" + replica.key + "`") //finally create all def fakeTraces2 = com.exalate.util.TraceUtils.indexFakeTraces(traces) def preparedIssue2 = hohInternal2.prepareLocalHubIssueForApplication(issueBeforeScript, issue, fakeTraces2) //@Nonnull IIssueKey issueKey, @Nonnull IHubIssueReplica hubIssueAfterScripts, @Nullable String proxyUser, @Nonnull IHubIssueReplica hubIssueBeforeScripts, @Nonnull Map> traces, @Nonnull List blobMetadataList, IRelation relation def resultTraces2 = null try{ resultTraces2 = hohInternal2.updateNodeIssueWith(exIssueKey, preparedIssue2, proxyAppUserInternal2.key, issueBeforeScript, fakeTraces2, blobMetadataList, syncRequest) } catch (MissingMethodException e){ resultTraces2 = hohInternal2.updateNodeIssueWith(exIssueKey, preparedIssue2, proxyAppUserInternal2.key, issueBeforeScript, fakeTraces2, blobMetadataList, syncRequest.getConnection()) } traces.clear() traces.addAll(resultTraces2 ?: []) new Result(issue, traces) } catch (com.exalate.api.exception.IssueTrackerException ite2) { throw ite2 } catch (Exception e2) { throw new com.exalate.api.exception.IssueTrackerException(e2.message, e2) } } static Result update(com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, List traces, List blobMetadataList, com.atlassian.jira.issue.MutableIssue jIssue, com.exalate.basic.domain.BasicIssueKey exIssueKey) { doUpdate(replica, issue, syncRequest, nodeHelper, issueBeforeScript, traces, blobMetadataList, jIssue, exIssueKey) } static class Result { com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue List traces Result(com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, java.util.List traces) { this.issue = issue this.traces = traces } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~LOG IN EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private static class LogIn { static logIn(u) { def authCtx = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() try { //Jira 7 authCtx.setLoggedInUser(u) } catch (Exception ignore) { // Jira 6 //noinspection GroovyAssignabilityCheck authCtx.setLoggedInUser(u.getDirectoryUser()) } } static R tryLogInFinallyLogOut(Closure fn) { def authCtx = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() def proxyAppUser = getProxyUser() def loggedInUser = authCtx.getLoggedInUser() try { logIn(proxyAppUser) fn() } finally { logIn(loggedInUser) } } static getProxyUser() { def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) nserv.proxyUser } } }