PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/bulkedit/operation/BulkMigrateOperation.java

https://bitbucket.org/ahmed_bilal_360factors/jira7-core
Java | 288 lines | 220 code | 39 blank | 29 comment | 54 complexity | 76aba144e1e6c3fb1d9939ce333dcba1 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.jira.bulkedit.operation;
  2. import com.atlassian.collectors.CollectorsUtil;
  3. import com.atlassian.jira.config.SubTaskManager;
  4. import com.atlassian.jira.issue.Issue;
  5. import com.atlassian.jira.issue.IssueManager;
  6. import com.atlassian.jira.issue.MutableIssue;
  7. import com.atlassian.jira.issue.link.IssueLink;
  8. import com.atlassian.jira.task.context.Context;
  9. import com.atlassian.jira.user.ApplicationUser;
  10. import com.atlassian.jira.util.ErrorCollection;
  11. import com.atlassian.jira.util.I18nHelper;
  12. import com.atlassian.jira.web.bean.BulkEditBean;
  13. import com.atlassian.jira.web.bean.MultiBulkMoveBean;
  14. import com.atlassian.jira.workflow.WorkflowException;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import java.util.Collection;
  18. import java.util.HashSet;
  19. import java.util.Objects;
  20. import java.util.Optional;
  21. import java.util.Set;
  22. /**
  23. * Operation to Move issues from differing contexts to multiple target contexts.
  24. */
  25. public class BulkMigrateOperation implements ProgressAwareBulkOperation {
  26. public static final String OPERATION_NAME = "BulkMigrate";
  27. public static final String NAME_KEY = "bulk.move.operation.name";
  28. private static final String DESCRIPTION_KEY = "bulk.move.operation.description";
  29. private static final Logger log = LoggerFactory.getLogger(BulkMigrateOperation.class);
  30. private final BulkMoveOperation bulkMoveOperation;
  31. private final SubTaskManager subTaskManager;
  32. private final IssueManager issueManager;
  33. public BulkMigrateOperation(BulkMoveOperation bulkMoveOperation, SubTaskManager subTaskManager, IssueManager issueManager) {
  34. this.bulkMoveOperation = bulkMoveOperation;
  35. this.subTaskManager = subTaskManager;
  36. this.issueManager = issueManager;
  37. }
  38. @Override
  39. public boolean canPerform(final BulkEditBean bulkEditBean, final ApplicationUser remoteUser) {
  40. return bulkMoveOperation.canPerform(bulkEditBean, remoteUser);
  41. }
  42. @Override
  43. public void perform(final BulkEditBean rootBulkEditBean, final ApplicationUser applicationUser, Context taskContext)
  44. throws BulkOperationException {
  45. try {
  46. MultiBulkMoveBean multiBulkMoveBean = rootBulkEditBean.getRelatedMultiBulkMoveBean();
  47. for (final Object value : multiBulkMoveBean.getBulkEditBeans().values()) {
  48. BulkEditBean bulkEditBean = (BulkEditBean) value;
  49. log.debug("Performing move for project " + bulkEditBean.getTargetProject().getName() +
  50. " issue type: " + bulkEditBean.getTargetIssueType().getName());
  51. //This move changes the security level of the subtask, however the subtask move below, will overwrite this again
  52. // See JRA-13937 - Bulk Move does not update the Security Level of subtasks for more details
  53. bulkMoveOperation.moveIssuesAndIndex(bulkEditBean, applicationUser, taskContext);
  54. MultiBulkMoveBean relatedMultiBulkMoveBean = bulkEditBean.getRelatedMultiBulkMoveBean();
  55. if (relatedMultiBulkMoveBean != null && relatedMultiBulkMoveBean.getBulkEditBeans() != null) {
  56. for (final Object o : relatedMultiBulkMoveBean.getBulkEditBeans().values()) {
  57. BulkEditBean subTaskBulkEditBean = (BulkEditBean) o;
  58. log.info("subTaskBulkEditBean move for project " +
  59. subTaskBulkEditBean.getTargetProject().getName() + " issue type: " +
  60. subTaskBulkEditBean.getTargetIssueType().getName());
  61. bulkMoveOperation.moveIssuesAndIndex(subTaskBulkEditBean, applicationUser, taskContext);
  62. }
  63. }
  64. }
  65. } catch (Exception e) {
  66. throw new BulkOperationException(e);
  67. }
  68. }
  69. public Optional<Issue> isConcurrentIssueUpdate(BulkEditBean rootBulkEditBean) {
  70. final MultiBulkMoveBean multiBulkMoveBean = rootBulkEditBean.getRelatedMultiBulkMoveBean();
  71. for (final Object value : multiBulkMoveBean.getBulkEditBeans().values()) {
  72. BulkEditBean bulkEditBean = (BulkEditBean) value;
  73. Optional<Issue> updated = isConcurrentIssueUpdateSingleBulkEditBean(bulkEditBean);
  74. if( updated.isPresent() ) {
  75. return updated;
  76. }
  77. MultiBulkMoveBean relatedMultiBulkMoveBean = bulkEditBean.getRelatedMultiBulkMoveBean();
  78. if (relatedMultiBulkMoveBean != null && relatedMultiBulkMoveBean.getBulkEditBeans() != null) {
  79. for (final Object o : relatedMultiBulkMoveBean.getBulkEditBeans().values()) {
  80. BulkEditBean subTaskBulkEditBean = (BulkEditBean) o;
  81. updated = isConcurrentIssueUpdateSingleBulkEditBean(subTaskBulkEditBean);
  82. if( updated.isPresent() ) {
  83. return updated;
  84. }
  85. }
  86. }
  87. }
  88. return Optional.empty();
  89. }
  90. public Optional<Issue> isConcurrentIssueUpdateSingleBulkEditBean(BulkEditBean bulkEditBean) {
  91. final Collection selectedIssues = bulkEditBean.getSelectedIssues();
  92. for (final Object selectedIssue : selectedIssues) {
  93. final MutableIssue issue = (MutableIssue) selectedIssue;
  94. final MutableIssue newIssue = (MutableIssue) bulkEditBean.getTargetIssueObjects().get(issue);
  95. if( isConcurrentIssueUpdate(bulkEditBean, issue, newIssue) ) {
  96. return Optional.of(issue);
  97. }
  98. }
  99. return Optional.empty();
  100. }
  101. private boolean isConcurrentIssueUpdate(final BulkEditBean bulkEditBean, final MutableIssue issue, final MutableIssue newIssue) {
  102. if (!issue.isSubTask()) {
  103. if (newIssue.isSubTask() || !issue.getProjectId().equals(newIssue.getProjectId())) {
  104. final Set<Long> oldSubtaskIds = bulkEditBean.getSubTaskOfSelectedIssues().stream().filter(st -> st.getParentId().equals(issue.getId())).map(Issue::getId).collect(CollectorsUtil.toImmutableSet());
  105. final Set<Long> actualSubtasksIds = subTaskManager.getSubTaskIssueLinks(issue.getId()).stream().map(IssueLink::getDestinationId).collect(CollectorsUtil.toImmutableSet());
  106. if (!oldSubtaskIds.equals(actualSubtasksIds)) {
  107. String errorMsg = String.format("Unable to move parent issue '%d [%s]' - subtask have changed from %s to %s",
  108. issue.getId(), issue.getKey(), oldSubtaskIds.toString(), actualSubtasksIds);
  109. log.warn(errorMsg);
  110. return true;
  111. }
  112. }
  113. }
  114. if (isNewParentIssueChanged(issue, newIssue)) {
  115. String errorMsg = String.format("Concurrent update of new parent issue '%d [%s]' occured during move of '%d [%s]' issue",
  116. newIssue.getParentObject().getId(), newIssue.getParentObject().getKey(),
  117. issue.getId(), issue.getKey());
  118. log.warn(errorMsg);
  119. return true;
  120. }
  121. MutableIssue issueFromDb = issueManager.getIssueObject(issue.getId());
  122. if (isIssueChanged(issue, issueFromDb)) {
  123. String errorMsg = String.format("Concurrent update of issue '%d [%s]' occured during move",
  124. issue.getId(), issue.getKey());
  125. log.warn(errorMsg);
  126. return true;
  127. }
  128. return false;
  129. }
  130. /**
  131. * Checks if moved issue have changed during the bulk move process.
  132. * If parent issue, project, security level, issue type, workflow is changed then return true.
  133. *
  134. * @param issue Issue to perform check on.
  135. * @return true if issue have changed during the bulk move, false otherwise.
  136. */
  137. private boolean isIssueChanged(final Issue issue, final Issue issueFromDb) {
  138. if (issueFromDb == null) {
  139. return true;
  140. }
  141. final boolean projectChanged = !Objects.equals(issue.getProjectId(), issueFromDb.getProjectId());
  142. boolean securityLevelChanged = false;
  143. if (!issueFromDb.isSubTask()) {
  144. securityLevelChanged = !Objects.equals(issue.getSecurityLevelId(), issueFromDb.getSecurityLevelId());
  145. }
  146. final boolean changedIssueType = !issue.getIssueTypeId().equals(issueFromDb.getIssueTypeId());
  147. final boolean parentIssueChanged = !Objects.equals(issue.getParentId(), issueFromDb.getParentId());
  148. final boolean changedWorkflow = !issue.getWorkflowId().equals(issueFromDb.getWorkflowId());
  149. return securityLevelChanged || projectChanged || parentIssueChanged || changedIssueType || changedWorkflow;
  150. }
  151. /**
  152. * Checks that the parent issue has not changed.
  153. * <p>
  154. * <p>
  155. * When moved issue changes parent issue then we need to check if parent issue is not changed between processing
  156. * form and actual bulk move operation.
  157. * </p>
  158. *
  159. * @param issueOld original issue
  160. * @param issueNew destination issue
  161. * @return true if parent issue is changed and any value from: project id, issue type, security level, workflow id
  162. * has changed since form processing (concurrent update to the parent issue)
  163. */
  164. private boolean isNewParentIssueChanged(final MutableIssue issueOld, final MutableIssue issueNew) {
  165. Issue oldParent = issueOld.getParentObject();
  166. Issue newParent = issueNew.getParentObject();
  167. if ((oldParent == null && newParent == null) || (oldParent != null && newParent == null)) {
  168. //no destination parent = no change in destination parent
  169. return false;
  170. } else if (newParentSetOrParentChanged(oldParent, newParent)) {
  171. //set new parent issue, or change parent
  172. MutableIssue newParentFromDb = issueManager.getIssueObject(issueNew.getParentObject().getId());
  173. return isIssueChanged(newParent, newParentFromDb);
  174. }
  175. return false;
  176. }
  177. private boolean newParentSetOrParentChanged(final Issue oldParent, final Issue newParent) {
  178. return (oldParent == null && newParent != null) ||
  179. (oldParent != null && newParent != null && !oldParent.getId().equals(newParent.getId()));
  180. }
  181. @Override
  182. public int getNumberOfTasks(final BulkEditBean rootBulkEditBean) {
  183. int count = 0;
  184. for (final Object o1 : rootBulkEditBean.getRelatedMultiBulkMoveBean().getBulkEditBeans().values()) {
  185. BulkEditBean bulkEditBean = (BulkEditBean) o1;
  186. count += bulkEditBean.getSelectedIssues().size();
  187. MultiBulkMoveBean relatedMultiBulkMoveBean = bulkEditBean.getRelatedMultiBulkMoveBean();
  188. if (relatedMultiBulkMoveBean != null && relatedMultiBulkMoveBean.getBulkEditBeans() != null) {
  189. for (final Object o : relatedMultiBulkMoveBean.getBulkEditBeans().values()) {
  190. BulkEditBean subTaskBulkEditBean = (BulkEditBean) o;
  191. count += subTaskBulkEditBean.getSelectedIssues().size();
  192. }
  193. }
  194. }
  195. return count;
  196. }
  197. public void chooseContext(BulkEditBean rootBulkEditBean, ApplicationUser applicationUser, I18nHelper i18nHelper,
  198. ErrorCollection errors) {
  199. Set<String> allIssuesByKey = new HashSet<>();
  200. // Loop through the child BulkEditBeans and do a chooseContext() on each.
  201. for (final Object o : rootBulkEditBean.getRelatedMultiBulkMoveBean().getBulkEditBeans().values()) {
  202. BulkEditBean bulkEditBean = (BulkEditBean) o;
  203. bulkMoveOperation.chooseContext(bulkEditBean, applicationUser, i18nHelper, errors);
  204. allIssuesByKey.addAll(bulkEditBean.getSelectedIssues().stream().map(Issue::getKey).collect(CollectorsUtil.toImmutableSet()));
  205. }
  206. //check if no selected parent issue is in currently processed issues
  207. for (final Object o : rootBulkEditBean.getRelatedMultiBulkMoveBean().getBulkEditBeans().values()) {
  208. BulkEditBean bulkEditBean = (BulkEditBean) o;
  209. Optional.ofNullable(bulkEditBean.getParentIssueKey()).filter(key -> allIssuesByKey.contains(key))
  210. .ifPresent(
  211. key -> errors.addError(bulkEditBean.getKey() + "parentIssueKey", i18nHelper.getText("convert.issue.to.subtask.error.parentincurrentgroup"))
  212. );
  213. }
  214. }
  215. public void chooseContextNoValidate(BulkEditBean rootBulkEditBean, ApplicationUser applicationUser) {
  216. bulkMoveOperation
  217. .chooseContextNoValidate(rootBulkEditBean.getRelatedMultiBulkMoveBean().getCurrentBulkEditBean(),
  218. applicationUser);
  219. }
  220. public boolean isStatusValid(BulkEditBean rootBulkEditBean) {
  221. return bulkMoveOperation.isStatusValid(rootBulkEditBean.getRelatedMultiBulkMoveBean().getCurrentBulkEditBean());
  222. }
  223. public void setStatusFields(BulkEditBean rootBulkEditBean) throws WorkflowException {
  224. bulkMoveOperation.setStatusFields(rootBulkEditBean.getRelatedMultiBulkMoveBean().getCurrentBulkEditBean());
  225. }
  226. public void validatePopulateFields(BulkEditBean rootBulkEditBean, I18nHelper i18nHelper, ErrorCollection errors) {
  227. bulkMoveOperation.validatePopulateFields(
  228. rootBulkEditBean.getRelatedMultiBulkMoveBean().getCurrentBulkEditBean(), errors, i18nHelper);
  229. }
  230. @Override
  231. public String getNameKey() {
  232. return NAME_KEY;
  233. }
  234. @Override
  235. public String getDescriptionKey() {
  236. return DESCRIPTION_KEY;
  237. }
  238. @Override
  239. public String getOperationName() {
  240. return OPERATION_NAME;
  241. }
  242. @Override
  243. public String getCannotPerformMessageKey() {
  244. return BulkMoveOperation.CANNOT_PERFORM_MESSAGE_KEY;
  245. }
  246. public BulkMoveOperation getBulkMoveOperation() {
  247. return bulkMoveOperation;
  248. }
  249. }