Java tutorial
/* * This software was designed and created by Jason Carroll. * Copyright (c) 2002, 2003, 2004 Jason Carroll. * The author can be reached at jcarroll@cowsultants.com * ITracker website: http://www.cowsultants.com * ITracker forums: http://www.cowsultants.com/phpBB/index.php * * This program is free software; you can redistribute it and/or modify * it only under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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. */ package org.itracker.model.util; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.itracker.core.resources.ITrackerResources; import org.itracker.model.*; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.*; /** * Contains utilities used when displaying and processing issues. */ public class IssueUtilities { private static final Logger log = Logger.getLogger(IssueUtilities.class); public static final int FIELD_TYPE_SINGLE = 1; public static final int FIELD_TYPE_INDEXED = 2; public static final int FIELD_TYPE_MAP = 3; public static final int FIELD_ID = -1; public static final int FIELD_DESCRIPTION = -2; public static final int FIELD_STATUS = -3; public static final int FIELD_RESOLUTION = -4; public static final int FIELD_SEVERITY = -5; public static final int FIELD_CREATOR = -6; public static final int FIELD_CREATEDATE = -7; public static final int FIELD_OWNER = -8; public static final int FIELD_LASTMODIFIED = -9; public static final int FIELD_PROJECT = -10; public static final int FIELD_TARGET_VERSION = -11; public static final int FIELD_COMPONENTS = -12; public static final int FIELD_VERSIONS = -13; public static final int FIELD_ATTACHMENTDESCRIPTION = -14; public static final int FIELD_ATTACHMENTFILENAME = -15; public static final int FIELD_HISTORY = -16; protected static final int[] STANDARD_FIELDS = { FIELD_ID, FIELD_DESCRIPTION, FIELD_STATUS, FIELD_RESOLUTION, FIELD_SEVERITY, FIELD_CREATOR, FIELD_CREATEDATE, FIELD_OWNER, FIELD_LASTMODIFIED, FIELD_PROJECT, FIELD_TARGET_VERSION, FIELD_COMPONENTS, FIELD_VERSIONS, FIELD_ATTACHMENTDESCRIPTION, FIELD_ATTACHMENTFILENAME, FIELD_HISTORY }; public static final int STATUS_NEW = 100; public static final int STATUS_UNASSIGNED = 200; public static final int STATUS_ASSIGNED = 300; public static final int STATUS_RESOLVED = 400; public static final int STATUS_CLOSED = 500; // This marks the end of all status numbers. You can NOT add a status above // this number or // they will not be found. public static final int STATUS_END = 600; public static final int HISTORY_STATUS_REMOVED = -1; public static final int HISTORY_STATUS_AVAILABLE = 1; /** * Defines a related issue. Sample text: related to */ public static final int RELATION_TYPE_RELATED_P = 1; /** * Defines a related issue. Sample text: related to */ public static final int RELATION_TYPE_RELATED_C = 2; /** * Defines a duplicate issue. Sample text: duplicates */ public static final int RELATION_TYPE_DUPLICATE_P = 3; /** * Defines a duplicate issue. Sample text: duplicate of */ public static final int RELATION_TYPE_DUPLICATE_C = 4; /** * Defines a cloned issue. Sample text: cloned to */ public static final int RELATION_TYPE_CLONED_P = 5; /** * Defines a cloned issue. Sample text: cloned from */ public static final int RELATION_TYPE_CLONED_C = 6; /** * Defines a split issue. Sample text: split to */ public static final int RELATION_TYPE_SPLIT_P = 7; /** * Defines a split issue. Sample text: split from */ public static final int RELATION_TYPE_SPLIT_C = 8; /** * Defines a dependent issue. Sample text: dependents */ public static final int RELATION_TYPE_DEPENDENT_P = 9; /** * Defines a dependent issue. Sample text: depends on */ public static final int RELATION_TYPE_DEPENDENT_C = 10; public static final int NUM_RELATION_TYPES = 10; private static List<Configuration> resolutions = new ArrayList<Configuration>(); private static List<Configuration> severities = new ArrayList<Configuration>(); private static List<Configuration> statuses = new ArrayList<Configuration>(); private static List<CustomField> customFields = new ArrayList<CustomField>(); private static final Logger logger = Logger.getLogger(IssueUtilities.class); public IssueUtilities() { } public static String getOwnerName(User owner, Locale locale) { return (null != owner ? owner.getFullName() : ITrackerResources.getString("itracker.web.generic.unassigned", locale)); } public static int getFieldType(Integer fieldId) { if (fieldId != null) { if (fieldId > 0) { return FIELD_TYPE_MAP; } } return FIELD_TYPE_SINGLE; } public static String getFieldName(Integer fieldId) { if (fieldId == null) { return ""; } if (fieldId > 0) { return "customFields"; } switch (fieldId) { case FIELD_ID: return "id"; case FIELD_DESCRIPTION: return "description"; case FIELD_STATUS: return "status"; case FIELD_RESOLUTION: return "resolution"; case FIELD_SEVERITY: return "severity"; case FIELD_CREATOR: return "creatorId"; case FIELD_CREATEDATE: return "createdate"; case FIELD_OWNER: return "ownerId"; case FIELD_LASTMODIFIED: return "lastmodified"; case FIELD_PROJECT: return "projectId"; case FIELD_TARGET_VERSION: return "targetVersion"; case FIELD_COMPONENTS: return "components"; case FIELD_VERSIONS: return "versions"; case FIELD_ATTACHMENTDESCRIPTION: return "attachmentDescription"; case FIELD_ATTACHMENTFILENAME: return "attachment"; case FIELD_HISTORY: return "history"; default: return ""; } } public static String getFieldName(Integer fieldId, List<CustomField> customFields, Locale locale) { if (fieldId < 0) { return ITrackerResources.getString(getStandardFieldKey(fieldId), locale); } else { return CustomFieldUtilities.getCustomFieldName(fieldId, locale); } } public static String getStandardFieldKey(int fieldId) { switch (fieldId) { case FIELD_ID: return "itracker.web.attr.id"; case FIELD_DESCRIPTION: return "itracker.web.attr.description"; case FIELD_STATUS: return "itracker.web.attr.status"; case FIELD_RESOLUTION: return "itracker.web.attr.resolution"; case FIELD_SEVERITY: return "itracker.web.attr.severity"; case FIELD_CREATOR: return "itracker.web.attr.creator"; case FIELD_CREATEDATE: return "itracker.web.attr.createdate"; case FIELD_OWNER: return "itracker.web.attr.owner"; case FIELD_LASTMODIFIED: return "itracker.web.attr.lastmodified"; case FIELD_PROJECT: return "itracker.web.attr.project"; case FIELD_TARGET_VERSION: return "itracker.web.attr.target"; case FIELD_COMPONENTS: return "itracker.web.attr.components"; case FIELD_VERSIONS: return "itracker.web.attr.versions"; case FIELD_ATTACHMENTDESCRIPTION: return "itracker.web.attr.attachmentdescription"; case FIELD_ATTACHMENTFILENAME: return "itracker.web.attr.attachmentfilename"; case FIELD_HISTORY: return "itracker.web.attr.detaileddescription"; default: return "itracker.web.generic.unknown"; } } public static NameValuePair[] getStandardFields(Locale locale) { NameValuePair[] fieldNames = new NameValuePair[STANDARD_FIELDS.length]; for (int i = 0; i < STANDARD_FIELDS.length; i++) { fieldNames[i] = new NameValuePair( ITrackerResources.getString(getStandardFieldKey(STANDARD_FIELDS[i]), locale), Integer.toString(STANDARD_FIELDS[i])); } return fieldNames; } public static String getRelationName(IssueRelation.Type value) { return getRelationName(value, ITrackerResources.getLocale()); } public static String getRelationName(IssueRelation.Type value, Locale locale) { return StringUtils.defaultIfBlank( ITrackerResources.getString(ITrackerResources.KEY_BASE_ISSUE_RELATION + value.getCode(), locale), value.name()); } public static IssueRelation.Type getMatchingRelationType(IssueRelation.Type relationType) { switch (relationType) { case RELATED_P: return IssueRelation.Type.RELATED_C; case RELATED_C: return IssueRelation.Type.RELATED_P; case DUPLICATE_P: return IssueRelation.Type.DUPLICATE_C; case DUPLICATE_C: return IssueRelation.Type.DUPLICATE_P; case CLONED_P: return IssueRelation.Type.CLONED_C; case CLONED_C: return IssueRelation.Type.CLONED_P; case SPLIT_P: return IssueRelation.Type.SPLIT_C; case SPLIT_C: return IssueRelation.Type.SPLIT_P; case DEPENDENT_P: return IssueRelation.Type.DEPENDENT_C; case DEPENDENT_C: default: return IssueRelation.Type.DEPENDENT_P; } } public static String componentsToString(Issue issue) { StringBuilder value = new StringBuilder(); if (issue != null && issue.getComponents().size() > 0) { for (int i = 0; i < issue.getComponents().size(); i++) { value.append(i != 0 ? ", " : "").append(issue.getComponents().get(i).getName()); } } return value.toString(); } public static String versionsToString(Issue issue) { StringBuilder value = new StringBuilder(); if (issue != null && issue.getVersions().size() > 0) { for (int i = 0; i < issue.getVersions().size(); i++) { value.append(i != 0 ? ", " : "").append(issue.getVersions().get(i).getNumber()); } } return value.toString(); } public static String historyToString(Issue issue, SimpleDateFormat sdf) { StringBuilder value = new StringBuilder(); if (issue != null && issue.getHistory().size() > 0 && sdf != null) { for (int i = 0; i < issue.getHistory().size(); i++) { value.append(i != 0 ? "," : "").append(issue.getHistory().get(i).getDescription()).append(",") .append(issue.getHistory().get(i).getUser().getFirstName()); value.append(" ").append(issue.getHistory().get(i).getUser().getLastName()).append(",") .append(sdf.format(issue.getHistory().get(i).getLastModifiedDate())); } } return value.toString(); } public static String getStatusName(Integer value) { return getStatusName(value, ITrackerResources.getLocale()); } public static String getStatusName(Integer value, Locale locale) { return getStatusName(Integer.toString(value), locale); } public static String getStatusName(String value, Locale locale) { return StringUtils.defaultIfBlank( ITrackerResources.getString(ITrackerResources.KEY_BASE_STATUS + value, locale), value); } /** * getStatuses() needs to get implemented.. */ public static List<Configuration> getStatuses() { return statuses; } public static List<NameValuePair> getStatuses(Locale locale) { List<NameValuePair> statusStrings = new ArrayList<>(statuses.size()); for (Configuration status : statuses) { statusStrings.add(new NameValuePair(getStatusName(status.getValue(), locale), status.getValue())); } return statusStrings; } public static void setStatuses(List<Configuration> value) { statuses = (value == null ? new ArrayList<Configuration>() : value); } public static int getNumberStatuses() { return statuses.size(); } public static String getSeverityName(Integer value) { return StringUtils.defaultIfBlank(getSeverityName(value, ITrackerResources.getLocale()), String.valueOf(value)); } public static String getSeverityName(Integer value, Locale locale) { return StringUtils.defaultIfBlank(getSeverityName(Integer.toString(value), locale), String.valueOf(value)); } public static String getSeverityName(String value, Locale locale) { return StringUtils.defaultIfBlank( ITrackerResources.getString(ITrackerResources.KEY_BASE_SEVERITY + value, locale), String.valueOf(value)); } /** * Returns the list of the defined issue severities in the system. The array * returned is a cached list set from the setSeverities method. The actual * values are stored in the database and and can be obtained from the * ConfigurationService bean. * * @param locale the locale to return the severities as */ public static List<NameValuePair> getSeverities(Locale locale) { List<NameValuePair> severityStrings = new ArrayList<>(); for (Configuration severity : severities) { NameValuePair nvp = new NameValuePair(getSeverityName(severity.getValue(), locale), severity.getValue()); severityStrings.add(nvp); } return severityStrings; } public static void setSeverities(List<Configuration> value) { severities = (value == null ? new ArrayList<Configuration>() : value); } public static int getNumberSeverities() { return severities.size(); } /** * Compares the severity of two issues. The int returned will be negative if * the the severity of issue A is less than the severity of issue B, * positive if issue A is a higher severity than issue B, or 0 if the two * issues have the same severity. * * @param issueA IssueModel A * @param issueB IssueModel B */ public static int compareSeverity(Issue issueA, Issue issueB) { if (issueA == null && issueB == null) { return 0; } else if (issueA == null) { return -1; } else if (issueB == null) { return 1; } else { int issueAIndex = Integer.MAX_VALUE; int issueBIndex = Integer.MAX_VALUE; for (int i = 0; i < severities.size(); i++) { if (severities.get(i) != null) { if (severities.get(i).getValue().equalsIgnoreCase(Integer.toString(issueA.getSeverity()))) { issueAIndex = i; } if (severities.get(i).getValue().equalsIgnoreCase(Integer.toString(issueB.getSeverity()))) { issueBIndex = i; } } } if (issueAIndex > issueBIndex) { return -1; } else if (issueAIndex < issueBIndex) { return 1; } } return 0; } public static String getResolutionName(int value) { return getResolutionName(value, ITrackerResources.getLocale()); } public static String getResolutionName(int value, Locale locale) { return getResolutionName(Integer.toString(value), locale); } public static String getResolutionName(String value, Locale locale) { return ITrackerResources.getString(ITrackerResources.KEY_BASE_RESOLUTION + value, locale); } public static String checkResolutionName(String value, Locale locale) throws MissingResourceException { return ITrackerResources.getCheckForKey(ITrackerResources.KEY_BASE_RESOLUTION + value, locale); } /** * Returns the list of predefined resolutions in the system. The array * returned is a cached list set from the setResolutions method. The actual * values are stored in the database and and can be obtained from the * ConfigurationService bean. * * @param locale the locale to return the resolutions as */ public static List<NameValuePair> getResolutions(Locale locale) { final List<NameValuePair> resolutionStrings = new ArrayList<>(resolutions.size()); for (Configuration resolution : resolutions) { resolutionStrings.add( new NameValuePair(getResolutionName(resolution.getValue(), locale), resolution.getValue())); } return resolutionStrings; } /** * Sets the cached list of predefined resolutions. */ public static void setResolutions(List<Configuration> value) { resolutions = (value == null ? new ArrayList<Configuration>() : value); } public static String getActivityName(IssueActivityType type) { return getActivityName(type, ITrackerResources.getLocale()); } public static String getActivityName(IssueActivityType type, Locale locale) { return StringUtils.defaultIfBlank( ITrackerResources.getString("itracker.activity." + String.valueOf(type.name()), locale), type.name()); } /** * Returns the cached array of CustomFieldModels. * * @return an array of CustomFieldModels */ public static List<CustomField> getCustomFields() { return (customFields == null ? new ArrayList<CustomField>() : customFields); } /** * Sets the cached array of CustomFieldModels. * */ public static void setCustomFields(List<CustomField> value) { customFields = (value == null ? new ArrayList<CustomField>() : value); } /** * Returns the custom field with the supplied id. Any labels will be * localized to the system default locale. * * @param id the id of the field to return * @return the requested CustomField object, or a new field if not found */ public static CustomField getCustomField(Integer id) { return getCustomField(id, ITrackerResources.getLocale()); } /** * Returns the custom field with the supplied id value. Any labels will be * translated to the given locale. * * @param id the id of the field to return * @param locale the locale to initialize any labels with * @return the requested CustomField object, or a new field if not found */ public static CustomField getCustomField(Integer id, Locale locale) { CustomField retField = null; try { for (CustomField customField : customFields) { if (customField != null && customField.getId() != null && customField.getId().equals(id)) { retField = (CustomField) customField.clone(); break; } } } catch (CloneNotSupportedException cnse) { logger.error("Error cloning CustomField: " + cnse.getMessage()); } if (retField == null) { retField = new CustomField(); } return retField; } /** * Returns the total number of defined custom fields */ public static int getNumberCustomFields() { return customFields.size(); } /** * Returns true if the user has permission to view the requested issue. * * @param issue an IssueModel of the issue to check view permission for * @param user a User for the user to check permission for * @param permissions a HashMap of the users permissions */ public static boolean canViewIssue(Issue issue, User user, Map<Integer, Set<PermissionType>> permissions) { if (user == null) { if (log.isInfoEnabled()) { log.info("canViewIssue: missing argument. user is null returning false"); } return false; } return canViewIssue(issue, user.getId(), permissions); } /** * Returns true if the user has permission to view the requested issue. * * @param issue an IssueModel of the issue to check view permission for * @param userId the userId of the user to check permission for * @param permissions a HashMap of the users permissions */ public static boolean canViewIssue(Issue issue, Integer userId, Map<Integer, Set<PermissionType>> permissions) { if (issue == null || userId == null || permissions == null) { if (log.isInfoEnabled()) { log.info("canViewIssue: missing argument. issue: " + issue + ", userid: " + userId + ", permissions: " + permissions); } return false; } if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_VIEW_ALL)) { if (log.isDebugEnabled()) { log.debug("canViewIssue: issue: " + issue + ", user: " + userId + ", permission: " + PermissionType.ISSUE_VIEW_ALL); } return true; } boolean isUsersIssue = false; // I think owner & creator should always be able to view the issue // otherwise it makes no sense of creating the issue itself. // So put these checks before checking permissions for the whole project. if (issue.getCreator().getId().equals(userId)) { if (log.isDebugEnabled()) { log.debug("canViewIssue: issue: " + issue + ", user: " + userId + ", permission: is creator"); } isUsersIssue = true; } if (issue.getOwner() != null) { if (issue.getOwner().getId().equals(userId)) { if (log.isDebugEnabled()) { log.debug("canViewIssue: issue: " + issue + ", user: " + userId + ", permission: is owner"); } isUsersIssue = true; } } // TODO should be checking for // UserUtilities.hasPermission(permissions, issue.getProject() // .getId(), PermissionType.ISSUE_VIEW_USERS) if (isUsersIssue) { if (log.isDebugEnabled()) { log.debug("canViewIssue: issue: " + issue + ", user: " + userId + ", permission: isUsersIssue"); } return true; } if (log.isDebugEnabled()) { log.debug("canViewIssue: issue: " + issue + ", user: " + userId + ", permission: none matched"); } return false; } /** * Returns true if the user has permission to edit the requested issue. * * @param issue an IssueModel of the issue to check edit permission for * @param userId the userId of the user to check permission for * @param permissions a HashMap of the users permissions */ @Deprecated public static boolean canEditIssue(Issue issue, Integer userId, Map<Integer, Set<PermissionType>> permissions) { if (issue == null || userId == null || permissions == null) { if (log.isInfoEnabled()) { log.info("canEditIssue: missing argument. issue: " + issue + ", userid: " + userId + ", permissions: " + permissions); } return false; } if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_EDIT_ALL)) { if (log.isDebugEnabled()) { log.debug("canEditIssue: user " + userId + " has permission to edit issue " + issue.getId() + ":" + PermissionType.ISSUE_EDIT_ALL); } return true; } if (!UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_EDIT_USERS)) { if (log.isDebugEnabled()) { log.debug("canEditIssue: user " + userId + " has not permission to edit issue " + issue.getId() + ":" + PermissionType.ISSUE_EDIT_USERS); } return false; } if (issue.getCreator().getId().equals(userId)) { if (log.isDebugEnabled()) { log.debug("canEditIssue: user " + userId + " is creator of issue " + issue.getId() + ":"); } return true; } if (issue.getOwner() != null) { if (issue.getOwner().getId().equals(userId)) { if (log.isDebugEnabled()) { log.debug("canEditIssue: user " + userId + " is owner of issue " + issue.getId() + ":"); } return true; } } if (log.isDebugEnabled()) { log.debug("canEditIssue: user " + userId + " could not match permission, denied"); } return false; } /** * Returns true if the user can be assigned to this issue. * * @param issue an IssueModel of the issue to check assign permission for * @param userId the userId of the user to check permission for * @param permissions a HashMap of the users permissions */ @Deprecated public static boolean canBeAssignedIssue(Issue issue, Integer userId, Map<Integer, Set<PermissionType>> permissions) { if (issue == null || userId == null || permissions == null) { return false; } if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_EDIT_ALL)) { return true; } if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_EDIT_USERS)) { if (issue.getCreator().getId().equals(userId)) { return true; } else if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_ASSIGNABLE)) { return true; } else if (issue.getOwner().getId() != null && issue.getOwner().getId().equals(userId)) { return true; } } return false; } /** * Returns true if the user can unassign themselves from the issue. * * @param issue an IssueModel of the issue to check assign permission for * @param userId the userId of the user to check permission for * @param permissions a HashMap of the users permissions */ public static boolean canUnassignIssue(Issue issue, Integer userId, Map<Integer, Set<PermissionType>> permissions) { if (issue == null || userId == null || permissions == null) { return false; } if (UserUtilities.hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_ASSIGN_OTHERS)) { return true; } return issue.getOwner() != null && userId.equals(issue.getOwner().getId()) && UserUtilities .hasPermission(permissions, issue.getProject().getId(), PermissionType.ISSUE_UNASSIGN_SELF); } public static boolean hasIssueRelation(Issue issue, Integer relatedIssueId) { if (issue != null) { List<IssueRelation> relations = issue.getRelations(); for (IssueRelation relation : relations) { if (relation.getRelatedIssue().getId().equals(relatedIssueId)) { return true; } } } return false; } public static boolean hasIssueNotification(Issue issue, Integer userId) { return hasIssueNotification(issue, issue.getProject(), userId); } public static boolean hasHardNotification(Issue issue, Project project, Integer userId) { if (issue == null || userId == null) { return false; } if ((issue.getOwner() != null && issue.getOwner().getId().equals(userId)) || issue.getCreator().getId().equals(userId)) { return true; } if (project != null && project.getOwners() != null) { for (User user : project.getOwners()) { if (user.getId().equals(userId)) { return true; } } } Collection<Notification> notifications = issue.getNotifications(); if (notifications != null) { for (Notification notification : notifications) { if (notification.getUser().getId().equals(userId) && notification.getRole() != Notification.Role.IP) { return true; } } } return false; } /** * Evaluate if a certain user is notified on issue change. * <p/> * FIXME: Does not work for admin of unassigned-issue-projects owner, see portalhome.do */ public static boolean hasIssueNotification(Issue issue, Project project, Integer userId) { if (issue == null || userId == null) { return false; } if (hasHardNotification(issue, project, userId)) { return true; } Collection<Notification> notifications = issue.getNotifications(); if (notifications != null) { for (Notification notification : notifications) { if (notification.getUser().getId().equals(userId)) { return true; } } } return false; } public static URL getIssueURL(Issue issue, String baseURL) throws MalformedURLException { return getIssueURL(issue, new URL(baseURL + (StringUtils.endsWith(baseURL, "/") ? "" : "/"))); } public static URL getIssueURL(Issue issue, URL base) { try { if (null != base && null != issue) return new URL(base, "module-projects/view_issue.do?id=" + issue.getId()); } catch (MalformedURLException e) { log.error("could not create URL", e); } return null; } }