/src/main/resources/com/onresolve/jira/groovy/canned/admin/RenameUser443.groovy

https://bitbucket.org/sorin/jira-plugin-intellij · Groovy · 930 lines · 752 code · 114 blank · 64 comment · 118 complexity · f0a86a6a3d61c15da1cb9c7a386471cd MD5 · raw file

  1. package com.onresolve.jira.groovy.canned.admin
  2. import com.atlassian.jira.ComponentManager
  3. import com.atlassian.jira.util.ErrorCollection
  4. import com.onresolve.jira.groovy.canned.CannedScript
  5. import org.apache.log4j.Category
  6. import com.atlassian.query.clause.Clause
  7. import com.atlassian.query.clause.TerminalClauseImpl
  8. import com.atlassian.query.operand.Operand
  9. import com.atlassian.query.operand.SingleValueOperand
  10. import com.atlassian.query.clause.OrClause
  11. import com.atlassian.jira.issue.search.SearchRequestManager
  12. import com.atlassian.jira.issue.search.SearchRequest
  13. import com.atlassian.jira.bc.filter.SearchRequestService
  14. import com.atlassian.jira.util.collect.EnclosedIterable
  15. import com.atlassian.jira.util.Consumer
  16. import com.atlassian.query.clause.ClauseVisitor
  17. import com.atlassian.query.clause.AndClause
  18. import com.atlassian.query.clause.NotClause
  19. import com.atlassian.query.clause.TerminalClause
  20. import com.atlassian.query.operand.OperandVisitor
  21. import com.atlassian.query.operand.EmptyOperand
  22. import com.atlassian.query.operand.FunctionOperand
  23. import com.atlassian.query.operand.MultiValueOperand
  24. import com.atlassian.query.Query
  25. import com.atlassian.query.QueryImpl
  26. import com.atlassian.jira.issue.search.managers.SearchHandlerManager
  27. import com.atlassian.jira.jql.ClauseHandler
  28. import com.atlassian.jira.issue.CustomFieldManager
  29. import com.atlassian.jira.issue.customfields.CustomFieldType
  30. import com.atlassian.jira.issue.fields.UserField
  31. import com.atlassian.jira.bc.JiraServiceContextImpl
  32. import com.atlassian.core.ofbiz.util.CoreTransactionUtil
  33. import com.atlassian.jira.event.ClearCacheEvent
  34. import com.atlassian.event.api.EventPublisher
  35. import com.atlassian.jira.imports.project.parser.UserAssociationParser
  36. import org.ofbiz.core.entity.GenericValue
  37. import com.atlassian.jira.util.ImportUtils
  38. import com.atlassian.jira.issue.index.IssueIndexManager
  39. import com.atlassian.jira.ManagerFactory
  40. import com.atlassian.jira.issue.IssueManager
  41. import org.ofbiz.core.entity.DelegatorInterface
  42. import org.ofbiz.core.entity.EntityOperator
  43. import org.ofbiz.core.entity.EntityExpr
  44. import org.ofbiz.core.entity.EntityConditionList
  45. import com.atlassian.jira.security.roles.actor.UserRoleActorFactory
  46. import com.atlassian.jira.security.roles.CachingProjectRoleAndActorStore
  47. import com.atlassian.jira.notification.type.SingleUser
  48. import com.atlassian.jira.issue.security.IssueSecuritySchemeManagerImpl
  49. import com.atlassian.jira.permission.DefaultPermissionSchemeManager
  50. import com.atlassian.jira.notification.DefaultNotificationSchemeManager
  51. import com.atlassian.jira.issue.search.CachingSearchRequestStore
  52. import com.atlassian.jira.util.SimpleErrorCollection
  53. import com.atlassian.jira.util.BuildUtils
  54. import com.atlassian.query.clause.WasClause
  55. import com.atlassian.jira.user.util.UserUtil
  56. import com.atlassian.query.clause.ChangedClause
  57. public class RenameUser implements CannedScript {
  58. Category log = Category.getInstance(RenameUser.class)
  59. ComponentManager componentManager = ComponentManager.getInstance()
  60. UserUtil userUtil = componentManager.getUserUtil()
  61. SearchRequestManager searchRequestManager = componentManager.getSearchRequestManager()
  62. def searchRequestService = componentManager.getSearchRequestService()
  63. public static final String FIELD_FROM_USER_ID = 'FIELD_FROM_USER_ID'
  64. public static final String FIELD_TO_USER_ID = 'FIELD_TO_USER_ID'
  65. public static final String FIELD_MERGE = 'FIELD_MERGE'
  66. List msgs = []
  67. String msg
  68. Set<Long> reindexIssueIds = new HashSet()
  69. boolean preview = false
  70. Long nSearchReqsUpdated = 0
  71. boolean doMerge
  72. public String getName() {
  73. "Renames a user ID"
  74. }
  75. public String getDescription() {
  76. "Changes a user's ID without reindexing or restarting"
  77. }
  78. List getCategories() {
  79. ["ADMIN"]
  80. }
  81. public Boolean isFinalParamsPage(Map params) {
  82. true
  83. }
  84. public List getParameters(Map params) {
  85. [
  86. [
  87. Name: FIELD_FROM_USER_ID,
  88. Label: 'From user ID',
  89. Description: "Rename or merge from who?",
  90. ],
  91. [
  92. Name: FIELD_TO_USER_ID,
  93. Label: 'To user ID',
  94. Description: "Rename or merge to who?",
  95. ],
  96. [
  97. Name: FIELD_MERGE,
  98. Label: 'Merge',
  99. Type: 'radio',
  100. Description: "Merge the first user to the second user, rather than rename",
  101. Values: ["false": "Rename", "true": "Merge"],
  102. ],
  103. ]
  104. }
  105. ErrorCollection doValidate(Map params, boolean forPreview) {
  106. ErrorCollection errorCollection = new SimpleErrorCollection()
  107. String sourceUserId = params[FIELD_FROM_USER_ID] as String
  108. String targetUserId = params[FIELD_TO_USER_ID] as String
  109. if (! params[FIELD_MERGE]) {
  110. errorCollection.addError(FIELD_MERGE, "Please select an option")
  111. }
  112. doMerge = Boolean.parseBoolean(params[FIELD_MERGE] as String)
  113. [FIELD_FROM_USER_ID, FIELD_TO_USER_ID].each {String p ->
  114. if (! params[p]) {
  115. errorCollection.addError(p, "Please provide the user ID")
  116. }
  117. }
  118. if (errorCollection.hasAnyErrors()) {
  119. return errorCollection
  120. }
  121. if (! userUtil.getUser(sourceUserId as String)) {
  122. errorCollection.addError(FIELD_FROM_USER_ID, "Cannot find user ID")
  123. }
  124. if (sourceUserId == targetUserId) {
  125. errorCollection.addErrorMessage("From and to user can't be the same")
  126. }
  127. /* TODO: make rename/merge transparent
  128. boolean targetExists = userUtil.getUser(targetUserId as String) as Boolean
  129. if (doMerge && ! targetExists) {
  130. errorCollection.addError(FIELD_TO_USER_ID, "Target user ID must exist already for a merge")
  131. }
  132. */
  133. if (! doMerge && targetExists) {
  134. errorCollection.addError(FIELD_TO_USER_ID, "Target user ID must not exist already")
  135. }
  136. if (sourceUserId == componentManager.getJiraAuthenticationContext().getUser()?.name) {
  137. errorCollection.addError(FIELD_FROM_USER_ID, "You can't rename the logged in user")
  138. }
  139. errorCollection
  140. }
  141. Query transformSearchRequest(Query query, String sourceUser, String targetUser) {
  142. Clause whereClause = query.getWhereClause()
  143. Clause newWhereClause = whereClause.accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause
  144. // log.debug("original whereClause: " + query.whereClause.toString())
  145. // log.debug "new where clause: " + newWhereClause.toString()
  146. Query newQuery = new QueryImpl(newWhereClause)
  147. newQuery = new QueryImpl(newWhereClause, query.orderByClause, newQuery.queryString)
  148. newQuery
  149. }
  150. Map doScript(Map params) {
  151. String sourceUser = params[FIELD_FROM_USER_ID] as String
  152. String targetUser = params[FIELD_TO_USER_ID] as String
  153. doMerge = Boolean.parseBoolean(params[FIELD_MERGE] as String)
  154. // make sure they're not logged on??
  155. if (!preview) {
  156. msgs << "<b>Completed</b><br>"
  157. }
  158. updateGenericValues(sourceUser, targetUser)
  159. updateAllSearchRequests(sourceUser, targetUser)
  160. if (!preview) {
  161. msg = "Completed ${doMerge ? "merge" : "rename"} of $sourceUser to $targetUser"
  162. log.warn(msg)
  163. msgs << msg
  164. }
  165. params["output"] = "<pre>" + msgs.join("\n") + "<pre>"
  166. params
  167. }
  168. private def reindexIssue(IssueManager issueManager, Long issueId, Category log, IssueIndexManager indexManager) {
  169. GenericValue issue = issueManager.getIssue(issueId)
  170. boolean wasIndexing = ImportUtils.isIndexIssues();
  171. ImportUtils.setIndexIssues(true);
  172. if ((BuildUtils.getCurrentBuildNumber() as Long) < 614) {
  173. ManagerFactory.getCacheManager().flush(com.atlassian.jira.issue.cache.CacheManager.ISSUE_CACHE, issue)
  174. }
  175. log.debug("Reindex issue ${issue.key}")
  176. indexManager.reIndex(issue);
  177. ImportUtils.setIndexIssues(wasIndexing)
  178. }
  179. def updateGenericValues(String sourceUser, String targetUser) {
  180. boolean began = false
  181. DelegatorInterface gd = (DelegatorInterface) componentManager.getComponentInstanceOfType(DelegatorInterface.class)
  182. IssueIndexManager indexManager = ComponentManager.getInstance().getIndexManager()
  183. CachingProjectRoleAndActorStore cachingProjectRoleAndActorStore = ComponentManager.getComponentInstanceOfType(CachingProjectRoleAndActorStore.class) as CachingProjectRoleAndActorStore
  184. DefaultNotificationSchemeManager notificationSchemeManager = ComponentManager.getComponentInstanceOfType(DefaultNotificationSchemeManager.class) as DefaultNotificationSchemeManager
  185. DefaultPermissionSchemeManager defaultPermissionSchemeManager = ComponentManager.getComponentInstanceOfType(DefaultPermissionSchemeManager.class) as DefaultPermissionSchemeManager
  186. IssueSecuritySchemeManagerImpl securitySchemeManager = ComponentManager.getComponentInstanceOfType(IssueSecuritySchemeManagerImpl.class) as IssueSecuritySchemeManagerImpl
  187. CachingSearchRequestStore cachingSearchRequestStore = ComponentManager.getComponentInstanceOfType(CachingSearchRequestStore.class) as CachingSearchRequestStore
  188. IssueManager issueManager = componentManager.getIssueManager()
  189. if (! preview) {
  190. log.debug("Begin transaction")
  191. began = CoreTransactionUtil.begin();
  192. }
  193. try {
  194. msg = "Beginning ${doMerge ? "merge" : "rename"} of $sourceUser to $targetUser"
  195. log.warn(msg)
  196. msgs << msg
  197. // Rename the user record itself
  198. if ((BuildUtils.getCurrentBuildNumber() as Long) < 614) {
  199. List gvs = gd.findByAnd("OSUser", [name: sourceUser])
  200. if (! preview) {
  201. gvs.each { GenericValue usergv ->
  202. if (doMerge) {
  203. // todo: delete their associated property records
  204. usergv.remove()
  205. }
  206. else {
  207. usergv.set("name", targetUser)
  208. usergv.store()
  209. }
  210. }
  211. }
  212. // group memberships
  213. gvs = gd.findByAnd("OSMembership", [userName: sourceUser])
  214. msg = "Update ${gvs.size()} OSMembership records"
  215. log.debug(msg)
  216. msgs << msg
  217. if (!preview) {
  218. List<GenericValue> targetGvs = gd.findByAnd("OSMembership", [userName: targetUser])
  219. List<Object> targetGroups = targetGvs*.get("groupName")
  220. gvs.each {GenericValue gv ->
  221. if (doMerge && targetGroups.contains (gv.get("groupName"))) {
  222. gv.remove()
  223. }
  224. else {
  225. gv.set("userName", targetUser)
  226. gv.store()
  227. }
  228. }
  229. }
  230. }
  231. else {
  232. // todo: only look in jira internal directory
  233. List gvs = gd.findByAnd("User", [userName: sourceUser])
  234. gvs.each {GenericValue usergv ->
  235. if (! preview) {
  236. if (doMerge) {
  237. usergv.remove()
  238. }
  239. else {
  240. usergv.set("userName", targetUser)
  241. usergv.set("lowerUserName", targetUser.toLowerCase())
  242. usergv.store()
  243. }
  244. }
  245. }
  246. // group memberships
  247. gvs = gd.findByAnd("Membership", [childName: sourceUser, directoryId: 1, membershipType: "GROUP_USER"])
  248. msg = "Update ${gvs.size()} Membership records"
  249. log.debug(msg)
  250. msgs << msg
  251. if (!preview) {
  252. List<GenericValue> targetGvs = gd.findByAnd("Membership", [childName: targetUser, directoryId: 1, membershipType: "GROUP_USER"])
  253. List<Object> targetGroups = targetGvs*.get("parentName")
  254. gvs.each {GenericValue gv ->
  255. if (doMerge && targetGroups.contains (gv.get("parentName"))) {
  256. gv.remove()
  257. }
  258. else {
  259. gv.set("childName", targetUser)
  260. gv.set("lowerChildName", targetUser)
  261. gv.store()
  262. }
  263. }
  264. }
  265. }
  266. // trustedapp
  267. List gvs = gd.findByAnd("TrustedApplication", [createdBy: sourceUser])
  268. msg = "Update ${gvs.size()} TrustedApplication records"
  269. log.debug(msg)
  270. msgs << msg
  271. if (!preview) {
  272. gvs.each {GenericValue gv ->
  273. gv.set("createdBy", targetUser)
  274. gv.store()
  275. }
  276. }
  277. // SearchRequest ownership
  278. // if this is a merge, we may get dupe filter names. Not ideal but handled OK by the UI
  279. gvs = gd.findByOr("SearchRequest", [user: sourceUser, author: sourceUser])
  280. msg = "Update ${gvs.size()} SearchRequest records"
  281. log.debug(msg)
  282. msgs << msg
  283. if (!preview) {
  284. gvs.each {GenericValue gv ->
  285. if (gv.get("user")) {
  286. gv.set("user", targetUser)
  287. }
  288. if (gv.get("author")) {
  289. gv.set("author", targetUser)
  290. }
  291. cachingSearchRequestStore.removeFromCache(gv.getLong("id"))
  292. gv.store()
  293. }
  294. }
  295. // ColumnLayout - choose one or tother.
  296. gvs = gd.findByAnd("ColumnLayout", [username: sourceUser])
  297. msg = "Update ${gvs.size()} ColumnLayout records"
  298. List<GenericValue> targetGvs = gd.findByAnd("ColumnLayout", [username: targetUser])
  299. log.debug(msg)
  300. msgs << msg
  301. if (!preview) {
  302. gvs.each {GenericValue gv ->
  303. if (doMerge && targetGvs.find {it.getLong("searchrequest") == gv.getLong("searchrequest")}) {
  304. gd.removeRelated("ChildColumnLayoutItem", gv)
  305. gv.remove()
  306. }
  307. else {
  308. gv.set("username", targetUser)
  309. gv.store()
  310. }
  311. }
  312. }
  313. if (!doMerge) {
  314. gvs = gd.findByAnd("UserHistoryItem", [username: sourceUser])
  315. msg = "Update ${gvs.size()} UserHistoryItem records"
  316. log.debug(msg)
  317. msgs << msg
  318. if (!preview) {
  319. gvs.each {GenericValue gv ->
  320. gv.set("username", targetUser)
  321. gv.store()
  322. }
  323. }
  324. }
  325. gvs = gd.findByAnd("FavouriteAssociations", [username: sourceUser])
  326. targetGvs = gd.findByAnd("FavouriteAssociations", [username: targetUser])
  327. msg = "Update ${gvs.size()} FavouriteAssociations records"
  328. log.debug(msg)
  329. msgs << msg
  330. if (!preview) {
  331. List matchFields = ["entityType", "entityId"]
  332. gvs.each {GenericValue gv ->
  333. // favourite assocs, only fave if not already faved
  334. if (doMerge && targetGvs.find {
  335. it.getFields(matchFields) == gv.getFields(matchFields)
  336. }) {
  337. // favcount must be adjusted downwards when both are favouriting
  338. log.debug("Remove fav association record and decrement favourite count")
  339. GenericValue sharedEntityGv = gd.findByPrimaryKey(gv.get("entityType") as String, [id: gv.get("entityId")])
  340. sharedEntityGv.set("favCount", (sharedEntityGv.get("favCount") as Long) - 1)
  341. sharedEntityGv.store()
  342. gv.remove()
  343. }
  344. else {
  345. gv.set("username", targetUser)
  346. gv.store()
  347. }
  348. }
  349. }
  350. ["PortalPage", "FilterSubscription"].each {String entityName ->
  351. gvs = gd.findByAnd(entityName, [username: sourceUser])
  352. msg = "Update ${gvs.size()} $entityName records"
  353. log.debug(msg)
  354. msgs << msg
  355. if (!preview) {
  356. gvs.each {GenericValue gv ->
  357. gv.set("username", targetUser)
  358. gv.store()
  359. }
  360. }
  361. }
  362. // note: for versions prior to 4.1 the favouritesStoreCache needs to be flushed
  363. // Core issue fields
  364. gvs = gd.findByOr("Issue", [reporter: sourceUser, assignee: sourceUser])
  365. msg = "Update ${gvs.size()} Issue records"
  366. log.debug(msg)
  367. msgs << msg
  368. gvs.each {GenericValue gv ->
  369. if (!preview) {
  370. if (gv.get("reporter") == sourceUser) {
  371. gv.set("reporter", targetUser)
  372. }
  373. if (gv.get("assignee") == sourceUser) {
  374. gv.set("assignee", targetUser)
  375. }
  376. gv.store()
  377. }
  378. reindexIssueIds.add(gv.getLong("id"))
  379. }
  380. // issue transition history
  381. gvs = gd.findByOr("Action", [author: sourceUser, updateauthor: sourceUser])
  382. msg = "Update ${gvs.size()} Action records"
  383. log.debug(msg)
  384. msgs << msg
  385. if (!preview) {
  386. gvs.each {GenericValue gv ->
  387. if (gv.get("author") == sourceUser) {
  388. gv.set("author", targetUser)
  389. }
  390. if (gv.get("updateauthor") == sourceUser) {
  391. gv.set("updateauthor", targetUser)
  392. }
  393. gv.store()
  394. }
  395. }
  396. // change groups
  397. gvs = gd.findByAnd("ChangeGroup", [author: sourceUser])
  398. msg = "Update ${gvs.size()} ChangeGroup records"
  399. log.debug(msg)
  400. msgs << msg
  401. if (!preview) {
  402. gvs.each {GenericValue gv ->
  403. gv.set("author", targetUser)
  404. gv.store()
  405. }
  406. }
  407. // Change items
  408. // todo: custom field history items - problem is they are referenced by name which is not unique
  409. EntityConditionList conditionList = new EntityConditionList(
  410. [new EntityConditionList(
  411. [
  412. // Stoopid oracle can't do equals on a clob, LIKE shd be OK
  413. // alternative is to do the filtering in the code
  414. new EntityExpr("oldvalue", EntityOperator.LIKE, sourceUser),
  415. new EntityExpr("newvalue", EntityOperator.LIKE, sourceUser),
  416. ], EntityOperator.OR),
  417. new EntityConditionList([new EntityExpr("field", EntityOperator.IN, ["reporter", "assignee"])], EntityOperator.AND)
  418. ], EntityOperator.AND
  419. )
  420. gvs = gd.findByCondition("ChangeItem", conditionList, ["id", "field", "oldvalue", "newvalue"], Collections.<String> emptyList())
  421. msg = "Update ${gvs.size()} ChangeItem records"
  422. log.debug(msg)
  423. msgs << msg
  424. if (!preview) {
  425. gvs.each {GenericValue gv ->
  426. if (gv.get("oldvalue") == sourceUser) {
  427. gv.set("oldvalue", targetUser)
  428. }
  429. if (gv.get("newvalue") == sourceUser) {
  430. gv.set("newvalue", targetUser)
  431. }
  432. gv.store()
  433. }
  434. }
  435. // Watchers and voters
  436. gvs = gd.findByAnd("UserAssociation", getUserAssociationConditionList(sourceUser))
  437. targetGvs = gd.findByAnd("UserAssociation", getUserAssociationConditionList(targetUser))
  438. msg = "Update ${gvs.size()} UserAssociation records"
  439. log.debug(msg)
  440. msgs << msg
  441. gvs.each {GenericValue gv ->
  442. if (gv.get("sinkNodeEntity") == "Issue") {
  443. reindexIssueIds.add(gv.getLong("sinkNodeId"))
  444. }
  445. if (!preview) {
  446. gd.removeValue(gv)
  447. if (doMerge && targetGvs.find {
  448. it.get("sinkNodeId") == gv.get("sinkNodeId") &&
  449. it.get("sinkNodeEntity") == gv.get("sinkNodeEntity") &&
  450. it.get("associationType") == gv.get("associationType")
  451. }) {
  452. // do nothing as always remove
  453. log.debug("Removed userassoc record")
  454. }
  455. else {
  456. // gv.store() doesn't work because we're changing the primary key
  457. gv.set("sourceName", targetUser)
  458. gd.create(gv)
  459. }
  460. }
  461. }
  462. // roles
  463. gvs = gd.findByAnd("ProjectRoleActor", [roletype: UserRoleActorFactory.TYPE, roletypeparameter: sourceUser])
  464. targetGvs = gd.findByAnd("ProjectRoleActor", [roletype: UserRoleActorFactory.TYPE, roletypeparameter: targetUser])
  465. msg = "Update ${gvs.size()} ProjectRoleActor records"
  466. log.debug(msg)
  467. msgs << msg
  468. if (!preview) {
  469. gvs.each {GenericValue gv ->
  470. List matchFields = ["pid", "projectroleid"]
  471. /*
  472. if (doMerge && targetGvs.find {
  473. gv.getFields(matchFields) == it.getFields(matchFields)
  474. }) {
  475. log.debug("Remove role record")
  476. gv.remove()
  477. }
  478. else {
  479. */
  480. // we do always want to merge
  481. gv.set("roletypeparameter", targetUser)
  482. gv.store()
  483. //}
  484. }
  485. cachingProjectRoleAndActorStore.clearCaches()
  486. }
  487. // SharePermissions not required as cannot share with a user
  488. // worklogs
  489. gvs = gd.findByOr("Worklog", [author: sourceUser, updateauthor: sourceUser])
  490. msg = "Update ${gvs.size()} Worklog records"
  491. log.debug(msg)
  492. msgs << msg
  493. if (!preview) {
  494. gvs.each {GenericValue gv ->
  495. if (gv.get("author") == sourceUser) {
  496. gv.set("author", targetUser)
  497. }
  498. if (gv.get("updateauthor") == sourceUser) {
  499. gv.set("updateauthor", targetUser)
  500. }
  501. gv.store()
  502. }
  503. }
  504. // project and comp leads
  505. gvs = gd.findByAnd("Project", [lead: sourceUser])
  506. msg = "Update ${gvs.size()} Project records"
  507. log.debug(msg)
  508. msgs << msg
  509. if (!preview) {
  510. gvs.each {GenericValue gv ->
  511. gv.set("lead", targetUser)
  512. gv.store()
  513. }
  514. }
  515. gvs = gd.findByAnd("Component", [lead: sourceUser])
  516. msg = "Update ${gvs.size()} Component records"
  517. log.debug(msg)
  518. msgs << msg
  519. if (!preview) {
  520. gvs.each {GenericValue gv ->
  521. gv.set("lead", targetUser)
  522. gv.store()
  523. }
  524. }
  525. // notification schemes
  526. gvs = gd.findByAnd("Notification", [type: SingleUser.DESC, parameter: sourceUser])
  527. targetGvs = gd.findByAnd("Notification", [type: SingleUser.DESC, parameter: targetUser])
  528. msg = "Update ${gvs.size()} Notification records"
  529. log.debug(msg)
  530. msgs << msg
  531. if (!preview) {
  532. gvs.each {GenericValue gv ->
  533. if (doMerge && targetGvs.find {
  534. gv.getFields(["scheme", "eventTypeId", "type"]) == it.getFields(["scheme", "eventTypeId", "type"])
  535. }) {
  536. log.debug("Remove Notification record")
  537. gv.remove()
  538. }
  539. else {
  540. gv.set("parameter", targetUser)
  541. gv.store()
  542. }
  543. }
  544. notificationSchemeManager.flushProjectSchemes()
  545. }
  546. // permission schemes
  547. gvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: sourceUser])
  548. targetGvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: targetUser])
  549. msg = "Update ${gvs.size()} SchemePermissions records"
  550. log.debug(msg)
  551. msgs << msg
  552. if (!preview) {
  553. List matchFields = ['scheme', 'permission', 'type', 'parameter']
  554. gvs.each {GenericValue gv ->
  555. if (doMerge && targetGvs.find {
  556. gv.getFields(matchFields) == it.getFields(matchFields)
  557. }) {
  558. log.debug("Remove Notification record")
  559. gv.remove()
  560. }
  561. else {
  562. gv.set("parameter", targetUser)
  563. gv.store()
  564. }
  565. }
  566. defaultPermissionSchemeManager.flushProjectSchemes()
  567. defaultPermissionSchemeManager.flushSchemeEntities()
  568. }
  569. // issue security schemes
  570. gvs = gd.findByAnd("SchemeIssueSecurities", [type: "user", parameter: sourceUser])
  571. targetGvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: targetUser])
  572. msg = "Update ${gvs.size()} SchemeIssueSecurities records"
  573. log.debug(msg)
  574. msgs << msg
  575. if (!preview) {
  576. List matchFields = ['scheme', 'security', 'type', 'parameter']
  577. gvs.each {GenericValue gv ->
  578. if (doMerge && targetGvs.find {
  579. gv.getFields(matchFields) == it.getFields(matchFields)
  580. }) {
  581. log.debug("Remove Notification record")
  582. gv.remove()
  583. }
  584. else {
  585. gv.set("parameter", targetUser)
  586. gv.store()
  587. }
  588. }
  589. securitySchemeManager.flushProjectSchemes()
  590. }
  591. // user and multi-user custom fields
  592. gvs = gd.findByCondition("CustomField",
  593. new EntityExpr("customfieldtypekey", EntityOperator.IN,
  594. [
  595. "com.atlassian.jira.plugin.system.customfieldtypes:userpicker",
  596. "com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker"
  597. ])
  598. ,
  599. ["id"], Collections.<String> emptyList())
  600. Long cfValueKount = 0
  601. gvs.each {GenericValue gv ->
  602. gv.getRelatedByAnd("ChildCustomFieldValue", [stringvalue: sourceUser]).each {GenericValue cfGv ->
  603. cfValueKount++
  604. if (!preview) {
  605. cfGv.set("stringvalue", targetUser)
  606. cfGv.store()
  607. }
  608. reindexIssueIds.add(cfGv.getLong("issue"))
  609. }
  610. }
  611. msg = "Update $cfValueKount CustomFieldValue records"
  612. log.debug(msg)
  613. msgs << msg
  614. gvs = gd.findByAnd("OSHistoryStep", [caller: sourceUser])
  615. msg = "Update ${gvs.size()} OSHistoryStep records"
  616. log.debug(msg)
  617. msgs << msg
  618. if (!preview) {
  619. gvs.each {GenericValue gv ->
  620. gv.set("caller", targetUser)
  621. gv.store()
  622. }
  623. }
  624. // fileattachments
  625. gvs = gd.findByAnd("FileAttachment", [author: sourceUser])
  626. msg = "Update ${gvs.size()} FileAttachment records"
  627. log.debug(msg)
  628. msgs << msg
  629. if (!preview) {
  630. gvs.each {GenericValue gv ->
  631. gv.set("author", targetUser)
  632. gv.store()
  633. }
  634. }
  635. // -------------------------------------- Finalise --------------------------------------
  636. if (! preview) {
  637. log.debug("Commit transaction")
  638. CoreTransactionUtil.commit(began);
  639. // Only for 4.1 and above - clearing the other caches are not necessary above but kept there if
  640. // I decide to backport this
  641. log.debug("Publish clear cache event")
  642. ComponentManager.getComponentInstanceOfType(EventPublisher.class).publish(ClearCacheEvent.INSTANCE);
  643. // reindex issues
  644. reindexIssueIds.each {Long issueId ->
  645. reindexIssue(issueManager, issueId, log, indexManager);
  646. }
  647. }
  648. msg = "Reindex ${reindexIssueIds.size()} issues"
  649. log.debug(msg)
  650. msgs << msg
  651. }
  652. catch (Exception e) {
  653. log.error("Problem renaming user, rolling back")
  654. CoreTransactionUtil.rollback(began)
  655. throw e
  656. }
  657. finally {
  658. // not sure if we need anything here
  659. }
  660. }
  661. private List<EntityExpr> getUserAssociationConditionList(String userId) {
  662. List userAssocCList = [
  663. new EntityExpr("sourceName", EntityOperator.EQUALS, userId),
  664. new EntityExpr("associationType", EntityOperator.IN,
  665. [UserAssociationParser.ASSOCIATION_TYPE_WATCH_ISSUE, UserAssociationParser.ASSOCIATION_TYPE_VOTE_ISSUE]),
  666. ]
  667. return userAssocCList
  668. }
  669. def updateSingleRequest(SearchRequest sr, String sourceUser, String targetUser) {
  670. Query query = sr.getQuery()
  671. Query newQuery
  672. // A query may have no where clause and select everything... if so .accept dies
  673. if (query.getWhereClause()) {
  674. newQuery = transformSearchRequest(query, sourceUser, targetUser)
  675. }
  676. else {
  677. newQuery = query
  678. }
  679. if (query.whereClause.toString() != newQuery.whereClause.toString()) {
  680. // this is stupid but setting a queryString seems to blow away the whereClause, so we do this twice
  681. nSearchReqsUpdated++
  682. log.debug("Update sr name: " + sr.name)
  683. // log.debug("queryString: " + newQuery.queryString)
  684. // log.debug("newQuery: $newQuery")
  685. sr.setQuery(newQuery)
  686. JiraServiceContextImpl ctx = new JiraServiceContextImpl(userUtil.getUser(sr.ownerUserName))
  687. searchRequestService.validateFilterForUpdate(ctx, sr)
  688. if (ctx.errorCollection.hasAnyErrors()) {
  689. log.error("errors: " + ctx.getErrorCollection())
  690. }
  691. else {
  692. if (! preview) {
  693. searchRequestService.updateFilter(ctx, sr)
  694. }
  695. }
  696. }
  697. else {
  698. log.debug("No need to update name: " + sr.name)
  699. }
  700. }
  701. private def updateAllSearchRequests(String sourceUser, String targetUser) {
  702. EnclosedIterable<SearchRequest> srs = searchRequestManager.getAll()
  703. srs.foreach(new Consumer<SearchRequest>() {
  704. void consume(SearchRequest sr) {
  705. try {
  706. updateSingleRequest(sr, sourceUser, targetUser)
  707. } catch (Exception e) {
  708. log.error("Error checking or updating search request: $sr - this is not fatal but you may wish to check this manually", e)
  709. }
  710. }
  711. })
  712. msg = "Update parameters for $nSearchReqsUpdated search requests"
  713. log.debug(msg)
  714. msgs << msg
  715. }
  716. String getDescription(Map params, boolean forPreview) {
  717. preview = true
  718. Map out = doScript(params)
  719. out["output"]
  720. }
  721. public String getHelpUrl() {
  722. "https://studio.plugins.atlassian.com/wiki/display/GRV/Built-In+Scripts#Built-InScripts-RenameaUserID"
  723. }
  724. }
  725. private class RenamingClauseVisitor implements ClauseVisitor {
  726. Category log = Category.getInstance(RenamingClauseVisitor.class)
  727. SearchHandlerManager searchHandlerManager = ComponentManager.getComponentInstanceOfType(SearchHandlerManager.class) as SearchHandlerManager
  728. ComponentManager componentManager = ComponentManager.getInstance()
  729. CustomFieldManager customFieldManager = componentManager.getCustomFieldManager()
  730. String sourceUser
  731. String targetUser
  732. RenamingClauseVisitor(String sourceUser, String targetUser) {
  733. this.sourceUser = sourceUser
  734. this.targetUser = targetUser
  735. }
  736. Object visit(AndClause andClause) {
  737. Collection<Clause> newClauses = transformSubClauses(andClause)
  738. new AndClause(newClauses)
  739. }
  740. Object visit(NotClause notClause) {
  741. new NotClause(notClause.getSubClause().accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause)
  742. }
  743. Object visit(OrClause orClause) {
  744. Collection<Clause> newClauses = transformSubClauses(orClause)
  745. new OrClause(newClauses)
  746. }
  747. private Collection<Clause> transformSubClauses(Clause mainClause) {
  748. Collection<Clause> newClauses = []
  749. mainClause.getClauses().each {Clause clause ->
  750. newClauses.add(clause.accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause)
  751. }
  752. return newClauses
  753. }
  754. Object visit(TerminalClause clause) {
  755. final Collection<ClauseHandler> handlers = searchHandlerManager.getClauseHandler(clause.getName());
  756. Collection<String> fieldIds = searchHandlerManager.getFieldIds(clause.getName())
  757. // If there are multiple handers, eg field has multiple duplicate names, don't deal with it
  758. // ie multiple fields with the same name visible to this user
  759. if (fieldIds?.size() != 1) {
  760. return clause
  761. }
  762. String fieldId = fieldIds.toList().first()
  763. boolean isUserField = false
  764. if (fieldId.startsWith("customfield_")) {
  765. CustomFieldType cfType = customFieldManager.getCustomFieldObject(fieldId).getCustomFieldType()
  766. if (cfType instanceof UserField) {
  767. isUserField = true
  768. }
  769. }
  770. if (["reporter", "assignee"].contains(fieldId) || isUserField) {
  771. Operand operand = clause.getOperand()
  772. Operand newOp = operand.accept(new RenamingOperandVisitor(sourceUser, targetUser)) as Operand
  773. TerminalClauseImpl newTermClause = new TerminalClauseImpl(clause.name, clause.getOperator(), newOp)
  774. return newTermClause
  775. }
  776. clause
  777. }
  778. Object visit(WasClause wasClause) {
  779. // wasClause in 4.3 not handled at the moment
  780. wasClause
  781. }
  782. Object visit(ChangedClause changedClause) {
  783. // changed not handled
  784. return null
  785. }
  786. }
  787. private class RenamingOperandVisitor implements OperandVisitor {
  788. Category log = Category.getInstance(RenamingOperandVisitor.class)
  789. String sourceUser
  790. String targetUser
  791. RenamingOperandVisitor(String sourceUser, String targetUser) {
  792. this.sourceUser = sourceUser
  793. this.targetUser = targetUser
  794. }
  795. Object visit(EmptyOperand empty) {
  796. empty
  797. }
  798. Object visit(FunctionOperand function) {
  799. function
  800. }
  801. Object visit(MultiValueOperand multiValue) {
  802. List<Operand> newOps = multiValue.getValues().collectAll {Operand operand ->
  803. if (operand instanceof SingleValueOperand && operand.getStringValue() == sourceUser) {
  804. return new SingleValueOperand(targetUser)
  805. }
  806. operand
  807. }
  808. new MultiValueOperand(newOps)
  809. }
  810. Object visit(SingleValueOperand singleValueOperand) {
  811. if (singleValueOperand.getStringValue() == sourceUser) {
  812. return new SingleValueOperand(targetUser)
  813. }
  814. singleValueOperand
  815. }
  816. }