IssueUtilities.java
/*
* 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;
}
}