PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/atlassian/jconnect/jira/IssueActivityService.java

https://bitbucket.org/atlassian/jiraconnect-jiraplugin/
Java | 188 lines | 152 code | 19 blank | 17 comment | 15 complexity | da7cd80ec3f44cfe240d1eeb28bc900c MD5 | raw file
  1. package com.atlassian.jconnect.jira;
  2. import com.atlassian.crowd.embedded.api.User;
  3. import com.atlassian.jconnect.jira.customfields.BuiltInField;
  4. import com.atlassian.jconnect.rest.entities.CommentEntity;
  5. import com.atlassian.jconnect.rest.entities.IssueWithCommentsEntity;
  6. import com.atlassian.jconnect.rest.entities.IssuesWithCommentsEntity;
  7. import com.atlassian.jira.bc.issue.search.SearchService;
  8. import com.atlassian.jira.issue.CustomFieldManager;
  9. import com.atlassian.jira.issue.Issue;
  10. import com.atlassian.jira.issue.comments.Comment;
  11. import com.atlassian.jira.issue.comments.CommentManager;
  12. import com.atlassian.jira.issue.fields.CustomField;
  13. import com.atlassian.jira.issue.search.SearchException;
  14. import com.atlassian.jira.issue.search.SearchResults;
  15. import com.atlassian.jira.jql.builder.JqlClauseBuilder;
  16. import com.atlassian.jira.jql.builder.JqlQueryBuilder;
  17. import com.atlassian.jira.project.Project;
  18. import com.atlassian.jira.web.bean.PagerFilter;
  19. import com.atlassian.query.Query;
  20. import com.atlassian.query.order.SortOrder;
  21. import org.slf4j.Logger;
  22. import org.slf4j.LoggerFactory;
  23. import java.util.ArrayList;
  24. import java.util.Date;
  25. import java.util.LinkedList;
  26. import java.util.List;
  27. import java.util.regex.Matcher;
  28. import java.util.regex.Pattern;
  29. /**
  30. * Retrieves recent issue activity for a remote user.
  31. *
  32. */
  33. public class IssueActivityService
  34. {
  35. private static final Logger log = LoggerFactory.getLogger(IssueActivityService.class);
  36. private final CommentManager commentManager;
  37. private final SearchService searchService;
  38. private final UserHelper userHelper;
  39. private final JMCProjectService jmcProjectService;
  40. private final CustomFieldManager customFieldManager;
  41. /** Used to ensure there is no JQL injection in the UUID. */
  42. private static final Pattern PATTERN_UUID = Pattern.compile("([a-fA-F-\\d]{36})");
  43. /** Matches a projectname such as: AngryNerds, Angry-Nerds, Angry-Nerds124*/
  44. private static final Pattern PATTERN_PROJECT_NAME = Pattern.compile("([a-fA-F-\\d].*)");
  45. public IssueActivityService(CommentManager commentManager,
  46. SearchService searchService,
  47. UserHelper userHelper,
  48. CustomFieldManager customFieldManager,
  49. JMCProjectService jmcProjectService)
  50. {
  51. this.commentManager = commentManager;
  52. this.searchService = searchService;
  53. this.userHelper = userHelper;
  54. this.customFieldManager = customFieldManager;
  55. this.jmcProjectService = jmcProjectService;
  56. }
  57. /**
  58. * Gets all issues (and their comments) for a particular UUID if updates exist for at least 1 issue created by
  59. * the UUID.
  60. *
  61. * @param project the project to get the issues for
  62. * @param uuid the uuid that create the issue
  63. * @param sinceMillis the time in millis to search for updates from
  64. * @return if updates exist, all issues and their comments. if no updates exist since sinceMillis, an emptyp IssuesWithCommentsEntity is returned.
  65. */
  66. public IssuesWithCommentsEntity getIssuesWithCommentsIfUpdatesExists(final Project project, final String uuid, final long sinceMillis)
  67. {
  68. final IssuesWithCommentsEntity issuesWithComments =
  69. new IssuesWithCommentsEntity(new ArrayList<IssueWithCommentsEntity>(),
  70. System.currentTimeMillis(), !jmcProjectService.isCrashesEnabledFor(project));
  71. boolean issueHasUpdates = false;
  72. final User user = userHelper.getJMCSystemUser();
  73. if (user != null)
  74. {
  75. // CHECK FOR JQL INJECTION.
  76. if (!isValidUserParameter(uuid, PATTERN_UUID)) {
  77. return issuesWithComments;
  78. }
  79. final CustomField uuidField = this.customFieldManager.getCustomFieldObjectByName(BuiltInField.UUID.fieldName());
  80. if (uuidField == null) {
  81. log.warn("Custom field: " + BuiltInField.UUID.fieldName() + " is missing from this instance. Ensure JIRA Mobile Connect is enabled.");
  82. return null;
  83. }
  84. final JqlClauseBuilder userProjectClause =
  85. JqlQueryBuilder.newClauseBuilder().
  86. project().eq(project.getId()).and().
  87. customField(uuidField.getIdAsLong()).eq().string(uuid).and().
  88. reporter().eq().string(user.getName());
  89. final JqlClauseBuilder dateClause =
  90. JqlQueryBuilder.newClauseBuilder().
  91. updated().gtEq(new Date(sinceMillis));
  92. final Query query =
  93. JqlQueryBuilder.newBuilder().where().
  94. addClause(userProjectClause.buildClause()).
  95. and().
  96. addClause(dateClause.buildClause()).buildQuery();
  97. try {
  98. final long resultCount = searchService.searchCount(user, query);
  99. if (resultCount > 0)
  100. {
  101. // at least 1 issue has been updated by a non-mobile user, so retrieve and resend all data.
  102. issueHasUpdates = retrieveIssuesWithComments(userProjectClause, sinceMillis, issuesWithComments, user);
  103. }
  104. } catch (SearchException e) {
  105. log.error("Error looking for updates via JQL: " + query.getWhereClause(), e);
  106. }
  107. }
  108. if (log.isDebugEnabled()) {
  109. log.debug(uuid + " issue count " + issuesWithComments.getIssues().size());
  110. }
  111. return issueHasUpdates ?
  112. issuesWithComments : // if there are no updates, return an empty list.
  113. new IssuesWithCommentsEntity(new LinkedList<IssueWithCommentsEntity>(), System.currentTimeMillis(), !jmcProjectService.isCrashesEnabledFor(project));
  114. }
  115. private boolean retrieveIssuesWithComments(JqlClauseBuilder builder, long sinceMillis, IssuesWithCommentsEntity issuesWithComments, User user) {
  116. final Query query = JqlQueryBuilder.newBuilder(builder.buildQuery()).
  117. orderBy().updatedDate(SortOrder.DESC).
  118. buildQuery();
  119. try {
  120. final SearchResults searchResults = searchService.search(user, query, new PagerFilter(100)); // only ever return last 100 bits of feedback.
  121. final List<Issue> issues = searchResults.getIssues();
  122. boolean hasAtLeastOneUpdate = false;
  123. for (Issue issue : issues) {
  124. final List<CommentEntity> commentEntities = new ArrayList<CommentEntity>();
  125. boolean hasUpdates = false;
  126. List<Comment> comments = commentManager.getCommentsForUser(issue, user);
  127. for (Comment comment : comments) {
  128. final boolean systemUser = commentManager.isUserCommentAuthor(user, comment);
  129. if ((comment.getUpdated().getTime() > sinceMillis) && !systemUser) {
  130. hasUpdates = true;
  131. hasAtLeastOneUpdate = true;
  132. }
  133. CommentEntity commentEntity = new CommentEntity(comment.getAuthor(),
  134. systemUser,
  135. comment.getBody(),
  136. comment.getUpdated(),
  137. issue.getKey());
  138. commentEntities.add(commentEntity);
  139. }
  140. IssueWithCommentsEntity issueEntity = new IssueWithCommentsEntity(issue.getKey(),
  141. issue.getStatusObject().getName(),
  142. issue.getSummary(),
  143. issue.getDescription(),
  144. issue.getCreated(),
  145. issue.getUpdated(),
  146. commentEntities,
  147. hasUpdates);
  148. issuesWithComments.getIssues().add(issueEntity);
  149. }
  150. if (log.isDebugEnabled())
  151. log.debug(query.getWhereClause() + " issue count " + issuesWithComments.getIssues().size());
  152. return hasAtLeastOneUpdate;
  153. } catch (SearchException e) {
  154. log.error(e.getMessage(), e);
  155. }
  156. return false;
  157. }
  158. private boolean isValidUserParameter(String userEnteredString, Pattern matchPattern) {
  159. if (userEnteredString == null) {
  160. return false;
  161. }
  162. final Matcher matcher = matchPattern.matcher(userEnteredString);
  163. if (!matcher.matches()) {
  164. log.warn(userEnteredString + " is invalid.");
  165. return false;
  166. }
  167. return true;
  168. }
  169. }