Java tutorial
/*- * [LICENSE] * Taskboard * - - - * Copyright (C) 2015 - 2016 Objective Solutions * - - - * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * [/LICENSE] */ package objective.taskboard.data; import java.io.Serializable; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collections; import java.util.Date; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.SerializationUtils; import objective.taskboard.config.SpringContextBridge; import objective.taskboard.cycletime.CycleTime; import objective.taskboard.database.IssuePriorityService; import objective.taskboard.domain.IssueColorService; import objective.taskboard.domain.converter.CardVisibilityEvalService; import objective.taskboard.domain.converter.IssueTeamService; import objective.taskboard.domain.converter.IssueTeamService.TeamOrigin; import objective.taskboard.jira.MetadataService; import objective.taskboard.jira.ProjectService; import objective.taskboard.jira.data.Version; import objective.taskboard.jira.properties.JiraProperties; import objective.taskboard.jira.properties.JiraProperties.BallparkMapping; import objective.taskboard.repository.FilterCachedRepository; import objective.taskboard.utils.DateTimeUtils; public class Issue extends IssueScratch implements Serializable { private static final long serialVersionUID = 8513934402068368820L; private transient Issue parentCard; private transient Set<Issue> subtasks = new LinkedHashSet<>(); private transient JiraProperties jiraProperties; private transient MetadataService metaDataService; private transient IssueTeamService issueTeamService; private transient FilterCachedRepository filterRepository; private transient CardVisibilityEvalService cardVisibilityEvalService; private transient ProjectService projectService; private transient IssueColorService issueColorService; private transient CycleTime cycleTime; private transient IssuePriorityService issuePriorityService; public Issue(IssueScratch scratch, JiraProperties properties, MetadataService metadataService, IssueTeamService issueTeamService, FilterCachedRepository filterRepository, CycleTime cycleTime, CardVisibilityEvalService cardVisibilityEvalService, ProjectService projectService, IssueColorService issueColorService, IssuePriorityService issuePriorityService) { this.id = scratch.id; this.issueKey = scratch.issueKey; this.projectKey = scratch.projectKey; this.project = scratch.project; this.type = scratch.type; this.summary = scratch.summary; this.status = scratch.status; this.startDateStepMillis = scratch.startDateStepMillis; this.parent = scratch.parent; this.dependencies = scratch.dependencies; this.bugs = scratch.bugs; this.assignee = scratch.assignee; this.priority = scratch.priority; this.dueDate = scratch.dueDate; this.created = scratch.created; this.description = scratch.description; this.comments = scratch.comments; this.labels = scratch.labels; this.components = scratch.components; this.blocked = scratch.blocked; this.lastBlockReason = scratch.lastBlockReason; this.tshirtSizes = scratch.tshirtSizes; this.additionalEstimatedHours = scratch.additionalEstimatedHours; this.timeTracking = scratch.timeTracking; this.reporter = scratch.reporter; this.classOfService = scratch.classOfService; this.releaseId = scratch.releaseId; this.changelog = scratch.changelog; this.remoteIssueUpdatedDate = scratch.remoteIssueUpdatedDate; this.coAssignees = scratch.coAssignees; this.fixVersions = scratch.fixVersions; this.metaDataService = metadataService; this.jiraProperties = properties; this.issueTeamService = issueTeamService; this.filterRepository = filterRepository; this.cycleTime = cycleTime; this.cardVisibilityEvalService = cardVisibilityEvalService; this.projectService = projectService; this.issueColorService = issueColorService; this.issuePriorityService = issuePriorityService; this.worklogs = scratch.worklogs; this.assignedTeamsIds = scratch.assignedTeamsIds; this.extraFields = scratch.extraFields; } public Issue() { jiraProperties = SpringContextBridge.getBean(JiraProperties.class); metaDataService = SpringContextBridge.getBean(MetadataService.class); issueTeamService = SpringContextBridge.getBean(IssueTeamService.class); filterRepository = SpringContextBridge.getBean(FilterCachedRepository.class); cycleTime = SpringContextBridge.getBean(CycleTime.class); cardVisibilityEvalService = SpringContextBridge.getBean(CardVisibilityEvalService.class); projectService = SpringContextBridge.getBean(ProjectService.class); issueColorService = SpringContextBridge.getBean(IssueColorService.class); issuePriorityService = SpringContextBridge.getBean(IssuePriorityService.class); } public boolean isDeferred() { boolean isDeferredStatus = jiraProperties.getStatusesDeferredIds().contains(this.getStatus()); if (!isDeferredStatus && parentCard != null) return parentCard.isDeferred(); return isDeferredStatus; } public String getTShirtSize() { String mainTShirtSizeFieldId = jiraProperties.getCustomfield().getTShirtSize().getMainTShirtSizeFieldId(); return Optional.ofNullable(tshirtSizes.get(mainTShirtSizeFieldId)) .flatMap(customField -> Optional.ofNullable((String) customField.getValue())).orElse(null); } public String getTshirtSizeOfSubtaskForBallpark(BallparkMapping mapping) { return Optional.ofNullable(tshirtSizes.get(mapping.getTshirtCustomFieldId())) .flatMap(customField -> Optional.ofNullable((String) customField.getValue())).orElse(null); } public List<BallparkMapping> getActiveBallparkMappings() { List<BallparkMapping> list = jiraProperties.getFollowup().getBallparkMappings().get(getType()); if (list == null) return null; return list.stream().filter(bm -> getTshirtSizeOfSubtaskForBallpark(bm) != null) .collect(Collectors.toList()); } public String getParentSummary() { if (this.parentCard != null) return this.parentCard.getSummary(); return ""; } public String getColor() { return issueColorService.getColor(getClassOfServiceId()); } public Set<String> getMismatchingUsers() { return issueTeamService.getMismatchingUsers(this); } public Set<CardTeam> getTeams() { return issueTeamService.resolveTeams(this); } /** * Returns the value of assigned teams id in jira field. The default team will never be returned here. * * @return the list of team id values in the jira issue. Prefer using getTeams to find the *actual* teams. */ public List<Long> getRawAssignedTeamsIds() { return assignedTeamsIds; } public boolean isUsingDefaultTeam() { return issueTeamService.resolveTeamsOrigin(this) == TeamOrigin.DEFAULT_BY_PROJECT; } public boolean isUsingTeamByIssueType() { return issueTeamService.resolveTeamsOrigin(this) == TeamOrigin.DEFAULT_BY_ISSUE_TYPE; } public boolean isUsingParentTeam() { return issueTeamService.resolveTeamsOrigin(this) == TeamOrigin.INHERITED; } public void setParentCard(Issue parentCard) { this.parentCard = parentCard; if (parentCard != null) parentCard.addsubtask(this); } private void addsubtask(Issue issue) { this.subtasks.remove(issue); this.subtasks.add(issue); } public void unlinkParent() { if (parentCard != null) parentCard.subtasks.remove(this); parentCard = null; } public Optional<Issue> getParentCard() { return Optional.ofNullable(parentCard); } public String getClassOfServiceValue() { String defaultClassOfService = jiraProperties.getCustomfield().getClassOfService().getDefaultValue(); CustomField classOfService = getClassOfServiceCustomField(); return classOfService == null ? defaultClassOfService : (String) classOfService.getValue(); } public String getClassOfServiceFieldId() { return jiraProperties.getCustomfield().getClassOfService().getId(); } private Long getClassOfServiceId() { CustomField classOfService = getClassOfServiceCustomField(); return classOfService == null ? 0L : classOfService.getOptionId(); } private CustomField getClassOfServiceCustomField() { String defaultClassOfService = jiraProperties.getCustomfield().getClassOfService().getDefaultValue(); CustomField classOfService = getLocalClassOfServiceCustomField(); boolean isNotDefaultClassOfService = classOfService != null && classOfService.getValue() != null && !classOfService.getValue().toString().equals(defaultClassOfService); if (isNotDefaultClassOfService) return classOfService; Optional<Issue> pc = getParentCard(); if (pc.isPresent()) return pc.get().getClassOfServiceCustomField(); return classOfService; } public void setClassOfServiceValue(final String classOfService) { this.classOfService.setValue(classOfService); } public List<Changelog> getChangelog() { return changelog; } public boolean isDemand() { return jiraProperties.getIssuetype().getDemand().getId() == this.getType(); } public boolean isFeature() { return jiraProperties.getIssuetype().getFeatures().stream().anyMatch(ft -> ft.getId() == this.getType()); } public boolean isSubTask() { return jiraProperties.getIssuetype().getSubtasks().stream().anyMatch(ft -> ft.getId() == this.getType()); } public Integer getIssueKeyNum() { return Integer.parseInt(issueKey.replace(projectKey + "-", "")); } public String getIssueTypeName() { return metaDataService.getIssueTypeById(type).getName(); } public String getIssueTypeNameAsLoggedInUser() { return metaDataService.getIssueTypeByIdAsLoggedInUser(type).getName(); } public String getStatusName() { return metaDataService.getStatusById(status).name; } public String getStatusNameAsLoggedInUser() { return metaDataService.getStatusByIdAsLoggedInUser(status).name; } public Integer getStatusPriority() { Integer r; if (this.isDemand()) r = jiraProperties.getStatusPriorityOrder().getDemandPriorityByStatus(this.getStatusName()); else if (this.isFeature()) r = jiraProperties.getStatusPriorityOrder().getTaskPriorityByStatus(this.getStatusName()); else r = jiraProperties.getStatusPriorityOrder().getSubtaskPriorityByStatus(this.getStatusName()); if (r == null) r = 0; return r; } public Optional<Double> getCycleTime(ZoneId timezone) { return cycleTime.getCycleTime(Instant.ofEpochMilli(startDateStepMillis), timezone, status); } public CustomField getAdditionalEstimatedHoursField() { return additionalEstimatedHours; } public Double getAdditionalEstimatedHours() { CustomField additionalEstimatedHours = getAdditionalEstimatedHoursField(); if (additionalEstimatedHours != null) return (Double) additionalEstimatedHours.getValue(); return null; } public boolean isCancelled() { return jiraProperties.getStatusesCanceledIds().stream().anyMatch(s -> s.equals(status)); } public boolean isCompleted() { return jiraProperties.getStatusesCompletedIds().stream().anyMatch(s -> s.equals(status)); } public boolean isBlocked() { return blocked; } public String getLastBlockReason() { return lastBlockReason; } public List<CustomField> getBallparks() { List<BallparkMapping> list = jiraProperties.getFollowup().getBallparkMappings().get(getType()); if (list == null || list.isEmpty()) { return Collections.emptyList(); } else { return list.stream().map(obj -> makeTshirtField(obj.getTshirtCustomFieldId())) .collect(Collectors.toList()); } } private CustomField makeTshirtField(String fieldId) { if (tshirtSizes.containsKey(fieldId)) { return tshirtSizes.get(fieldId); } else { return new CustomField(fieldId, null); } } public String getCardTshirtSize() { if (tshirtSizes.containsKey(jiraProperties.getCustomfield().getTShirtSize().getMainTShirtSizeFieldId())) return "" + tshirtSizes.get(jiraProperties.getCustomfield().getTShirtSize().getMainTShirtSizeFieldId()) .getValue(); return ""; } public String getCardTshirtSizeFieldId() { return jiraProperties.getCustomfield().getTShirtSize().getMainTShirtSizeFieldId(); } public Long getId() { return this.id; } public String getProjectKey() { return this.projectKey; } public String getProject() { return this.project; } public long getType() { return this.type; } public String getTypeIconUri() { return metaDataService.getIssueTypeById(type).getIconUri().toASCIIString(); } public String getSummary() { return this.summary; } public long getStatus() { return this.status; } public long getStartDateStepMillis() { return this.startDateStepMillis; } public String getParent() { return this.parent; } public long getParentType() { return getParentCard().map(parent -> parent.type).orElse(0L); } public String getParentTypeIconUri() { return getParentCard().map(Issue::getTypeIconUri).orElse(""); } public List<String> getDependencies() { return this.dependencies; } public List<String> getBugs() { return bugs; } public List<User> getCoAssignees() { return this.coAssignees; } public List<Version> getFixVersions() { return fixVersions; } public List<User> getAssignees() { LinkedList<User> assigneeSet = new LinkedList<>(); if (getAssignee().isAssigned()) assigneeSet.add(getAssignee()); assigneeSet.addAll(getCoAssignees()); return assigneeSet; } public User getAssignee() { return this.assignee; } public long getPriority() { return this.priority; } public Date getDueDate() { return this.dueDate; } public ZonedDateTime getDueDateByTimezoneId(ZoneId timezone) { return DateTimeUtils.get(dueDate, timezone); } public Date getUpdatedDate() { Date priorityUpdatedDate = getPriorityUpdatedDate(); if (remoteIssueUpdatedDate == null || priorityUpdatedDate.after(remoteIssueUpdatedDate)) return priorityUpdatedDate; return remoteIssueUpdatedDate; } public ZonedDateTime getUpdatedDateByTimezoneId(ZoneId timezone) { return DateTimeUtils.get(getUpdatedDate(), timezone); } public long getCreated() { return this.created; } public ZonedDateTime getCreatedDateByTimezoneId(ZoneId timezone) { return DateTimeUtils.get(this.created, timezone); } public String getDescription() { return this.description; } public List<Comment> getComments() { return this.comments; } public List<String> getLabels() { return this.labels; } public List<String> getComponents() { return this.components; } public long getPriorityOrder() { return issuePriorityService.determinePriority(this); } public TaskboardTimeTracking getTimeTracking() { return this.timeTracking; } public void setId(final Long id) { this.id = id; } public void setIssueKey(final String issueKey) { this.issueKey = issueKey; } public void setProjectKey(final String projectKey) { this.projectKey = projectKey; } public void setProject(final String project) { this.project = project; } public void setType(final long type) { this.type = type; } public void setSummary(final String summary) { this.summary = summary; } public void setStatus(final long status) { this.status = status; } public void setParent(final String parent) { this.parent = parent; } public void setDependencies(final List<String> dependencies) { this.dependencies = dependencies; } public void setBugs(final List<String> bugs) { this.bugs = bugs; } public void setAssignee(final User assignee) { this.assignee = assignee; } public void setPriority(final long priority) { this.priority = priority; } public void setRemoteIssueUpdatedDate(final Date remoteIssueUpdatedDate) { this.remoteIssueUpdatedDate = remoteIssueUpdatedDate; } public Date getRemoteIssueUpdatedDate() { return remoteIssueUpdatedDate; } public Date getPriorityUpdatedDate() { return issuePriorityService.priorityUpdateDate(this); } public void setCreated(final long created) { this.created = created; } public void setDescription(final String description) { this.description = description; } public void setComments(final List<Comment> comments) { this.comments = comments; } public void setLabels(final LinkedList<String> labels) { this.labels = labels; } public void setComponents(final List<String> components) { this.components = components; } public String getReporter() { return reporter; } public void setReporter(String nameReporter) { this.reporter = nameReporter; } public CustomField getLocalClassOfServiceCustomField() { return classOfService; } public boolean isVisible() { if (isDeferred()) return false; boolean isVisible = filterRepository.getCache().stream().anyMatch(f -> f.isApplicable(this)); if (!isVisible) return false; return cardVisibilityEvalService.isStillInVisibleRange(status, getUpdatedDate().toInstant(), changelog); } public Set<Issue> getSubtaskCards() { return subtasks; } public List<Subtask> getSubtasks() { return subtasks.stream().sorted((s1, s2) -> compareIssueKey(s1.issueKey, s2.issueKey)) .map(s -> new Subtask(s.issueKey, s.summary, s.getStatusNameAsLoggedInUser(), s.getIssueTypeNameAsLoggedInUser(), s.getTypeIconUri(), issueColorService.getStatusColor(s.type, s.status))) .collect(Collectors.toList()); } static int compareIssueKey(String issueKey1, String issueKey2) { try { Pattern pattern = Pattern.compile("^(.*?)-([0-9]*)$"); Matcher mk1 = pattern.matcher(issueKey1); Matcher mk2 = pattern.matcher(issueKey2); if (!mk1.matches() || !mk2.matches()) return 0; int projComp = mk1.group(1).compareTo(mk2.group(1)); if (projComp != 0) return projComp; return Integer.parseInt(mk1.group(2)) - Integer.parseInt(mk2.group(2)); } catch (NumberFormatException e) { return 0; } } public String getReleaseId() { if (releaseId != null) return releaseId; Optional<Issue> pc = getParentCard(); return pc.map(issue -> issue.getReleaseId()).orElse(null); } public Version getRelease() { return projectService.getVersion(getReleaseId()); } public Map<String, String> getExtraFields() { return extraFields; } public List<Worklog> getWorklogs() { return worklogs; } private Object readResolve() { this.subtasks = new LinkedHashSet<>(); return this; } public void restoreServices(JiraProperties jiraProperties, MetadataService metaDataService, IssueTeamService issueTeamService, FilterCachedRepository filterRepository, CycleTime cycleTime, CardVisibilityEvalService cardVisibilityEvalService, ProjectService projectService, IssueColorService issueColorService, IssuePriorityService issuePriorityService) { this.jiraProperties = jiraProperties; this.metaDataService = metaDataService; this.issueTeamService = issueTeamService; this.filterRepository = filterRepository; this.cycleTime = cycleTime; this.cardVisibilityEvalService = cardVisibilityEvalService; this.projectService = projectService; this.issueColorService = issueColorService; this.issuePriorityService = issuePriorityService; } @Override public int hashCode() { if (this.issueKey == null) return 0; return this.issueKey.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Issue) { Issue other = (Issue) obj; return (issueKey + "").equals(other.issueKey + ""); } return false; } public void addTeam(Team teamToAdd) { if (isUsingDefaultTeam()) assignedTeamsIds.add(issueTeamService.getDefaultTeamId(this)); else if (isUsingTeamByIssueType()) { Optional<CardTeam> teamByIssueType = issueTeamService.getCardTeamByIssueType(this); if (teamByIssueType.isPresent()) assignedTeamsIds.add(teamByIssueType.get().id); } else if (isUsingParentTeam()) assignedTeamsIds.addAll(parentCard.getRawAssignedTeamsIds()); if (!assignedTeamsIds.contains(teamToAdd.getId())) assignedTeamsIds.add(teamToAdd.getId()); } public void removeTeam(Team teamToRemove) { if (isUsingParentTeam()) { this.assignedTeamsIds.addAll(parentCard.getRawAssignedTeamsIds()); } assignedTeamsIds.remove(teamToRemove.getId()); } public void replaceTeam(Optional<Team> teamToReplace, Team replacementTeam) { if (teamToReplace.isPresent()) { int previousPos = assignedTeamsIds.indexOf(teamToReplace.get().getId()); if (previousPos > -1) { assignedTeamsIds.set(previousPos, replacementTeam.getId()); return; } } assignedTeamsIds.add(replacementTeam.getId()); } public static class CardTeam { public String name; public Long id; public CardTeam() { } public CardTeam(Long i) { id = i; } public CardTeam(String name, Long i) { this.name = name; id = i; } public static CardTeam from(Team team) { CardTeam card = new CardTeam(); card.name = team.getName(); card.id = team.getId(); return card; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CardTeam other = (CardTeam) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } public Issue copy() { Issue copy = SerializationUtils.clone(this); restoreServicesToIssue(copy); if (this.parentCard != null) { Issue parentCopy = this.parentCard.copy(); copy.setParentCard(parentCopy); } return copy; } private void restoreServicesToIssue(Issue issue) { issue.restoreServices(jiraProperties, metaDataService, issueTeamService, filterRepository, cycleTime, cardVisibilityEvalService, projectService, issueColorService, issuePriorityService); } public void restoreDefaultTeams() { this.assignedTeamsIds.clear(); } }