IssueUtilities.java

  1. /*
  2.  * This software was designed and created by Jason Carroll.
  3.  * Copyright (c) 2002, 2003, 2004 Jason Carroll.
  4.  * The author can be reached at jcarroll@cowsultants.com
  5.  * ITracker website: http://www.cowsultants.com
  6.  * ITracker forums: http://www.cowsultants.com/phpBB/index.php
  7.  *
  8.  * This program is free software; you can redistribute it and/or modify
  9.  * it only under the terms of the GNU General Public License as published by
  10.  * the Free Software Foundation; either version 2 of the License, or
  11.  * (at your option) any later version.
  12.  *
  13.  * This program is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  * GNU General Public License for more details.
  17.  */

  18. package org.itracker.model.util;

  19. import org.apache.commons.lang.StringUtils;
  20. import org.apache.log4j.Logger;
  21. import org.itracker.core.resources.ITrackerResources;
  22. import org.itracker.model.*;

  23. import java.net.MalformedURLException;
  24. import java.net.URL;
  25. import java.text.SimpleDateFormat;
  26. import java.util.*;

  27. /**
  28.  * Contains utilities used when displaying and processing issues.
  29.  */
  30. public class IssueUtilities {

  31.     private static final Logger log = Logger.getLogger(IssueUtilities.class);
  32.     public static final int FIELD_TYPE_SINGLE = 1;
  33.     public static final int FIELD_TYPE_INDEXED = 2;
  34.     public static final int FIELD_TYPE_MAP = 3;

  35.     public static final int FIELD_ID = -1;
  36.     public static final int FIELD_DESCRIPTION = -2;
  37.     public static final int FIELD_STATUS = -3;
  38.     public static final int FIELD_RESOLUTION = -4;
  39.     public static final int FIELD_SEVERITY = -5;
  40.     public static final int FIELD_CREATOR = -6;
  41.     public static final int FIELD_CREATEDATE = -7;
  42.     public static final int FIELD_OWNER = -8;
  43.     public static final int FIELD_LASTMODIFIED = -9;
  44.     public static final int FIELD_PROJECT = -10;
  45.     public static final int FIELD_TARGET_VERSION = -11;
  46.     public static final int FIELD_COMPONENTS = -12;
  47.     public static final int FIELD_VERSIONS = -13;
  48.     public static final int FIELD_ATTACHMENTDESCRIPTION = -14;
  49.     public static final int FIELD_ATTACHMENTFILENAME = -15;
  50.     public static final int FIELD_HISTORY = -16;

  51.     protected static final int[] STANDARD_FIELDS = {FIELD_ID,
  52.             FIELD_DESCRIPTION, FIELD_STATUS, FIELD_RESOLUTION, FIELD_SEVERITY,
  53.             FIELD_CREATOR, FIELD_CREATEDATE, FIELD_OWNER, FIELD_LASTMODIFIED,
  54.             FIELD_PROJECT, FIELD_TARGET_VERSION, FIELD_COMPONENTS,
  55.             FIELD_VERSIONS, FIELD_ATTACHMENTDESCRIPTION,
  56.             FIELD_ATTACHMENTFILENAME, FIELD_HISTORY};

  57.     public static final int STATUS_NEW = 100;
  58.     public static final int STATUS_UNASSIGNED = 200;
  59.     public static final int STATUS_ASSIGNED = 300;
  60.     public static final int STATUS_RESOLVED = 400;
  61.     public static final int STATUS_CLOSED = 500;

  62.     // This marks the end of all status numbers. You can NOT add a status above
  63.     // this number or
  64.     // they will not be found.
  65.     public static final int STATUS_END = 600;

  66.     public static final int HISTORY_STATUS_REMOVED = -1;
  67.     public static final int HISTORY_STATUS_AVAILABLE = 1;

  68.     /**
  69.      * Defines a related issue. Sample text: related to
  70.      */
  71.     public static final int RELATION_TYPE_RELATED_P = 1;
  72.     /**
  73.      * Defines a related issue. Sample text: related to
  74.      */
  75.     public static final int RELATION_TYPE_RELATED_C = 2;
  76.     /**
  77.      * Defines a duplicate issue. Sample text: duplicates
  78.      */
  79.     public static final int RELATION_TYPE_DUPLICATE_P = 3;
  80.     /**
  81.      * Defines a duplicate issue. Sample text: duplicate of
  82.      */
  83.     public static final int RELATION_TYPE_DUPLICATE_C = 4;
  84.     /**
  85.      * Defines a cloned issue. Sample text: cloned to
  86.      */
  87.     public static final int RELATION_TYPE_CLONED_P = 5;
  88.     /**
  89.      * Defines a cloned issue. Sample text: cloned from
  90.      */
  91.     public static final int RELATION_TYPE_CLONED_C = 6;
  92.     /**
  93.      * Defines a split issue. Sample text: split to
  94.      */
  95.     public static final int RELATION_TYPE_SPLIT_P = 7;
  96.     /**
  97.      * Defines a split issue. Sample text: split from
  98.      */
  99.     public static final int RELATION_TYPE_SPLIT_C = 8;
  100.     /**
  101.      * Defines a dependent issue. Sample text: dependents
  102.      */
  103.     public static final int RELATION_TYPE_DEPENDENT_P = 9;
  104.     /**
  105.      * Defines a dependent issue. Sample text: depends on
  106.      */
  107.     public static final int RELATION_TYPE_DEPENDENT_C = 10;

  108.     public static final int NUM_RELATION_TYPES = 10;

  109.     private static List<Configuration> resolutions = new ArrayList<Configuration>();
  110.     private static List<Configuration> severities = new ArrayList<Configuration>();
  111.     private static List<Configuration> statuses = new ArrayList<Configuration>();
  112.     private static List<CustomField> customFields = new ArrayList<CustomField>();
  113.     private static final Logger logger = Logger.getLogger(IssueUtilities.class);

  114.     public IssueUtilities() {
  115.     }

  116.     public static String getOwnerName(User owner, Locale locale) {
  117.         return (null != owner? owner.getFullName():
  118.                 ITrackerResources.getString("itracker.web.generic.unassigned", locale));
  119.     }

  120.     public static int getFieldType(Integer fieldId) {
  121.         if (fieldId != null) {
  122.             if (fieldId > 0) {
  123.                 return FIELD_TYPE_MAP;
  124.             }

  125.         }

  126.         return FIELD_TYPE_SINGLE;
  127.     }

  128.     public static String getFieldName(Integer fieldId) {
  129.         if (fieldId == null) {
  130.             return "";
  131.         }

  132.         if (fieldId > 0) {
  133.             return "customFields";
  134.         }

  135.         switch (fieldId) {
  136.             case FIELD_ID:
  137.                 return "id";
  138.             case FIELD_DESCRIPTION:
  139.                 return "description";
  140.             case FIELD_STATUS:
  141.                 return "status";
  142.             case FIELD_RESOLUTION:
  143.                 return "resolution";
  144.             case FIELD_SEVERITY:
  145.                 return "severity";
  146.             case FIELD_CREATOR:
  147.                 return "creatorId";
  148.             case FIELD_CREATEDATE:
  149.                 return "createdate";
  150.             case FIELD_OWNER:
  151.                 return "ownerId";
  152.             case FIELD_LASTMODIFIED:
  153.                 return "lastmodified";
  154.             case FIELD_PROJECT:
  155.                 return "projectId";
  156.             case FIELD_TARGET_VERSION:
  157.                 return "targetVersion";
  158.             case FIELD_COMPONENTS:
  159.                 return "components";
  160.             case FIELD_VERSIONS:
  161.                 return "versions";
  162.             case FIELD_ATTACHMENTDESCRIPTION:
  163.                 return "attachmentDescription";
  164.             case FIELD_ATTACHMENTFILENAME:
  165.                 return "attachment";
  166.             case FIELD_HISTORY:
  167.                 return "history";
  168.             default:
  169.                 return "";
  170.         }
  171.     }

  172.     public static String getFieldName(Integer fieldId,
  173.                                       List<CustomField> customFields, Locale locale) {

  174.         if (fieldId < 0) {
  175.             return ITrackerResources.getString(getStandardFieldKey(fieldId), locale);
  176.         } else {
  177.             return CustomFieldUtilities.getCustomFieldName(fieldId,
  178.                     locale);
  179.         }

  180.     }

  181.     public static String getStandardFieldKey(int fieldId) {
  182.         switch (fieldId) {
  183.             case FIELD_ID:
  184.                 return "itracker.web.attr.id";
  185.             case FIELD_DESCRIPTION:
  186.                 return "itracker.web.attr.description";
  187.             case FIELD_STATUS:
  188.                 return "itracker.web.attr.status";
  189.             case FIELD_RESOLUTION:
  190.                 return "itracker.web.attr.resolution";
  191.             case FIELD_SEVERITY:
  192.                 return "itracker.web.attr.severity";
  193.             case FIELD_CREATOR:
  194.                 return "itracker.web.attr.creator";
  195.             case FIELD_CREATEDATE:
  196.                 return "itracker.web.attr.createdate";
  197.             case FIELD_OWNER:
  198.                 return "itracker.web.attr.owner";
  199.             case FIELD_LASTMODIFIED:
  200.                 return "itracker.web.attr.lastmodified";
  201.             case FIELD_PROJECT:
  202.                 return "itracker.web.attr.project";
  203.             case FIELD_TARGET_VERSION:
  204.                 return "itracker.web.attr.target";
  205.             case FIELD_COMPONENTS:
  206.                 return "itracker.web.attr.components";
  207.             case FIELD_VERSIONS:
  208.                 return "itracker.web.attr.versions";
  209.             case FIELD_ATTACHMENTDESCRIPTION:
  210.                 return "itracker.web.attr.attachmentdescription";
  211.             case FIELD_ATTACHMENTFILENAME:
  212.                 return "itracker.web.attr.attachmentfilename";
  213.             case FIELD_HISTORY:
  214.                 return "itracker.web.attr.detaileddescription";
  215.             default:
  216.                 return "itracker.web.generic.unknown";
  217.         }
  218.     }

  219.     public static NameValuePair[] getStandardFields(Locale locale) {
  220.         NameValuePair[] fieldNames = new NameValuePair[STANDARD_FIELDS.length];
  221.         for (int i = 0; i < STANDARD_FIELDS.length; i++) {
  222.             fieldNames[i] = new NameValuePair(ITrackerResources.getString(
  223.                     getStandardFieldKey(STANDARD_FIELDS[i]), locale), Integer
  224.                     .toString(STANDARD_FIELDS[i]));
  225.         }
  226.         return fieldNames;
  227.     }

  228.     public static String getRelationName(IssueRelation.Type value) {
  229.         return getRelationName(value, ITrackerResources.getLocale());
  230.     }


  231.     public static String getRelationName(IssueRelation.Type value, Locale locale) {
  232.         return StringUtils.defaultIfBlank(ITrackerResources.getString(
  233.                 ITrackerResources.KEY_BASE_ISSUE_RELATION + value.getCode(), locale), value.name());
  234.     }

  235.     public static IssueRelation.Type getMatchingRelationType(IssueRelation.Type relationType) {
  236.         switch (relationType) {
  237.             case RELATED_P:
  238.                 return IssueRelation.Type.RELATED_C;
  239.             case RELATED_C:
  240.                 return IssueRelation.Type.RELATED_P;
  241.             case DUPLICATE_P:
  242.                 return IssueRelation.Type.DUPLICATE_C;
  243.             case DUPLICATE_C:
  244.                 return IssueRelation.Type.DUPLICATE_P;
  245.             case CLONED_P:
  246.                 return IssueRelation.Type.CLONED_C;
  247.             case CLONED_C:
  248.                 return IssueRelation.Type.CLONED_P;
  249.             case SPLIT_P:
  250.                 return IssueRelation.Type.SPLIT_C;
  251.             case SPLIT_C:
  252.                 return IssueRelation.Type.SPLIT_P;
  253.             case DEPENDENT_P:
  254.                 return IssueRelation.Type.DEPENDENT_C;
  255.             case DEPENDENT_C:
  256.             default:
  257.                 return IssueRelation.Type.DEPENDENT_P;
  258.         }
  259.     }

  260.     public static String componentsToString(Issue issue) {
  261.         StringBuilder value = new StringBuilder();
  262.         if (issue != null && issue.getComponents().size() > 0) {
  263.             for (int i = 0; i < issue.getComponents().size(); i++) {
  264.                 value.append(i != 0 ? ", " : "").append(issue.getComponents().get(i).getName());
  265.             }
  266.         }
  267.         return value.toString();
  268.     }

  269.     public static String versionsToString(Issue issue) {
  270.         StringBuilder value = new StringBuilder();
  271.         if (issue != null && issue.getVersions().size() > 0) {
  272.             for (int i = 0; i < issue.getVersions().size(); i++) {
  273.                 value.append(i != 0 ? ", " : "").append(issue.getVersions().get(i).getNumber());
  274.             }
  275.         }
  276.         return value.toString();
  277.     }

  278.     public static String historyToString(Issue issue, SimpleDateFormat sdf) {
  279.         StringBuilder value = new StringBuilder();
  280.         if (issue != null && issue.getHistory().size() > 0 && sdf != null) {
  281.             for (int i = 0; i < issue.getHistory().size(); i++) {
  282.                 value.append(i != 0 ? "," : "").append(issue.getHistory().get(i).getDescription()).append(",").append(issue.getHistory().get(i).getUser().getFirstName());
  283.                 value.append(" ").append(issue.getHistory().get(i).getUser().getLastName()).append(",").append(sdf.format(issue.getHistory().get(i)
  284.                         .getLastModifiedDate()));
  285.             }
  286.         }
  287.         return value.toString();
  288.     }

  289.     public static String getStatusName(Integer value) {
  290.         return getStatusName(value, ITrackerResources.getLocale());
  291.     }

  292.     public static String getStatusName(Integer value, Locale locale) {
  293.         return getStatusName(Integer.toString(value), locale);
  294.     }

  295.     public static String getStatusName(String value, Locale locale) {
  296.         return StringUtils.defaultIfBlank(ITrackerResources.getString(ITrackerResources.KEY_BASE_STATUS
  297.                 + value, locale), value);
  298.     }

  299.     /**
  300.      * getStatuses() needs to get implemented..
  301.      */
  302.     public static List<Configuration> getStatuses() {
  303.         return statuses;
  304.     }

  305.     public static List<NameValuePair> getStatuses(Locale locale) {
  306.         List<NameValuePair> statusStrings = new ArrayList<>(statuses.size());
  307.         for (Configuration status : statuses) {
  308.             statusStrings.add(new NameValuePair(getStatusName(status.getValue(), locale), status.getValue()));
  309.         }
  310.         return statusStrings;
  311.     }

  312.     public static void setStatuses(List<Configuration> value) {
  313.         statuses = (value == null ? new ArrayList<Configuration>() : value);
  314.     }

  315.     public static int getNumberStatuses() {
  316.         return statuses.size();
  317.     }

  318.     public static String getSeverityName(Integer value) {
  319.         return StringUtils.defaultIfBlank(getSeverityName(value, ITrackerResources.getLocale()), String.valueOf(value));
  320.     }

  321.     public static String getSeverityName(Integer value, Locale locale) {
  322.         return StringUtils.defaultIfBlank(getSeverityName(Integer.toString(value), locale), String.valueOf(value));
  323.     }

  324.     public static String getSeverityName(String value, Locale locale) {
  325.         return StringUtils.defaultIfBlank(ITrackerResources.getString(ITrackerResources.KEY_BASE_SEVERITY
  326.                 + value, locale), String.valueOf(value));
  327.     }

  328.     /**
  329.      * Returns the list of the defined issue severities in the system. The array
  330.      * returned is a cached list set from the setSeverities method. The actual
  331.      * values are stored in the database and and can be obtained from the
  332.      * ConfigurationService bean.
  333.      *
  334.      * @param locale the locale to return the severities as
  335.      */
  336.     public static List<NameValuePair> getSeverities(Locale locale) {
  337.         List<NameValuePair> severityStrings = new ArrayList<>();

  338.         for (Configuration severity : severities) {
  339.             NameValuePair nvp = new NameValuePair(getSeverityName(severity.getValue(), locale),
  340.                     severity.getValue());
  341.             severityStrings.add(nvp);
  342.         }
  343.         return severityStrings;
  344.     }

  345.     public static void setSeverities(List<Configuration> value) {
  346.         severities = (value == null ? new ArrayList<Configuration>() : value);
  347.     }

  348.     public static int getNumberSeverities() {
  349.         return severities.size();
  350.     }

  351.     /**
  352.      * Compares the severity of two issues. The int returned will be negative if
  353.      * the the severity of issue A is less than the severity of issue B,
  354.      * positive if issue A is a higher severity than issue B, or 0 if the two
  355.      * issues have the same severity.
  356.      *
  357.      * @param issueA IssueModel A
  358.      * @param issueB IssueModel B
  359.      */
  360.     public static int compareSeverity(Issue issueA, Issue issueB) {
  361.         if (issueA == null && issueB == null) {
  362.             return 0;
  363.         } else if (issueA == null) {
  364.             return -1;
  365.         } else if (issueB == null) {
  366.             return 1;
  367.         } else {
  368.             int issueAIndex = Integer.MAX_VALUE;
  369.             int issueBIndex = Integer.MAX_VALUE;
  370.             for (int i = 0; i < severities.size(); i++) {
  371.                 if (severities.get(i) != null) {
  372.                     if (severities.get(i).getValue().equalsIgnoreCase(
  373.                             Integer.toString(issueA.getSeverity()))) {
  374.                         issueAIndex = i;
  375.                     }
  376.                     if (severities.get(i).getValue().equalsIgnoreCase(
  377.                             Integer.toString(issueB.getSeverity()))) {
  378.                         issueBIndex = i;
  379.                     }
  380.                 }
  381.             }
  382.             if (issueAIndex > issueBIndex) {
  383.                 return -1;
  384.             } else if (issueAIndex < issueBIndex) {
  385.                 return 1;
  386.             }
  387.         }

  388.         return 0;
  389.     }

  390.     public static String getResolutionName(int value) {
  391.         return getResolutionName(value, ITrackerResources.getLocale());
  392.     }

  393.     public static String getResolutionName(int value, Locale locale) {
  394.         return getResolutionName(Integer.toString(value), locale);
  395.     }

  396.     public static String getResolutionName(String value, Locale locale) {
  397.         return ITrackerResources.getString(
  398.                 ITrackerResources.KEY_BASE_RESOLUTION + value, locale);
  399.     }

  400.     public static String checkResolutionName(String value, Locale locale)
  401.             throws MissingResourceException {
  402.         return ITrackerResources.getCheckForKey(
  403.                 ITrackerResources.KEY_BASE_RESOLUTION + value, locale);
  404.     }

  405.     /**
  406.      * Returns the list of predefined resolutions in the system. The array
  407.      * returned is a cached list set from the setResolutions method. The actual
  408.      * values are stored in the database and and can be obtained from the
  409.      * ConfigurationService bean.
  410.      *
  411.      * @param locale the locale to return the resolutions as
  412.      */
  413.     public static List<NameValuePair> getResolutions(Locale locale) {
  414.         final List<NameValuePair> resolutionStrings = new ArrayList<>(resolutions.size());
  415.         for (Configuration resolution : resolutions) {
  416.             resolutionStrings.add(new NameValuePair(
  417.                     getResolutionName(resolution.getValue(), locale),
  418.                     resolution.getValue()));
  419.         }
  420.         return resolutionStrings;
  421.     }

  422.     /**
  423.      * Sets the cached list of predefined resolutions.
  424.      */
  425.     public static void setResolutions(List<Configuration> value) {
  426.         resolutions = (value == null ? new ArrayList<Configuration>() : value);
  427.     }

  428.     public static String getActivityName(IssueActivityType type) {
  429.         return getActivityName(type, ITrackerResources.getLocale());
  430.     }

  431.     public static String getActivityName(IssueActivityType type, Locale locale) {
  432.         return StringUtils.defaultIfBlank(ITrackerResources.getString("itracker.activity."
  433.                 + String.valueOf(type.name()), locale), type.name());
  434.     }

  435.     /**
  436.      * Returns the cached array of CustomFieldModels.
  437.      *
  438.      * @return an array of CustomFieldModels
  439.      */
  440.     public static List<CustomField> getCustomFields() {
  441.         return (customFields == null ? new ArrayList<CustomField>()
  442.                 : customFields);
  443.     }

  444.     /**
  445.      * Sets the cached array of CustomFieldModels.
  446.      *
  447.      */
  448.     public static void setCustomFields(List<CustomField> value) {
  449.         customFields = (value == null ? new ArrayList<CustomField>() : value);
  450.     }

  451.     /**
  452.      * Returns the custom field with the supplied id. Any labels will be
  453.      * localized to the system default locale.
  454.      *
  455.      * @param id the id of the field to return
  456.      * @return the requested CustomField object, or a new field if not found
  457.      */
  458.     public static CustomField getCustomField(Integer id) {
  459.         return getCustomField(id, ITrackerResources.getLocale());
  460.     }

  461.     /**
  462.      * Returns the custom field with the supplied id value. Any labels will be
  463.      * translated to the given locale.
  464.      *
  465.      * @param id     the id of the field to return
  466.      * @param locale the locale to initialize any labels with
  467.      * @return the requested CustomField object, or a new field if not found
  468.      */
  469.     public static CustomField getCustomField(Integer id, Locale locale) {
  470.         CustomField retField = null;

  471.         try {
  472.             for (CustomField customField : customFields) {
  473.                 if (customField != null
  474.                         && customField.getId() != null
  475.                         && customField.getId().equals(id)) {
  476.                     retField = (CustomField) customField.clone();
  477.                     break;
  478.                 }
  479.             }
  480.         } catch (CloneNotSupportedException cnse) {
  481.             logger.error("Error cloning CustomField: " + cnse.getMessage());
  482.         }
  483.         if (retField == null) {
  484.             retField = new CustomField();
  485.         }

  486.         return retField;
  487.     }

  488.     /**
  489.      * Returns the total number of defined custom fields
  490.      */
  491.     public static int getNumberCustomFields() {
  492.         return customFields.size();
  493.     }

  494.     /**
  495.      * Returns true if the user has permission to view the requested issue.
  496.      *
  497.      * @param issue       an IssueModel of the issue to check view permission for
  498.      * @param user        a User for the user to check permission for
  499.      * @param permissions a HashMap of the users permissions
  500.      */
  501.     public static boolean canViewIssue(Issue issue, User user,
  502.                                        Map<Integer, Set<PermissionType>> permissions) {
  503.         if (user == null) {
  504.             if (log.isInfoEnabled()) {
  505.                 log
  506.                         .info("canViewIssue: missing argument. user is null returning false");
  507.             }
  508.             return false;
  509.         }
  510.         return canViewIssue(issue, user.getId(), permissions);
  511.     }


  512.     /**
  513.      * Returns true if the user has permission to view the requested issue.
  514.      *
  515.      * @param issue       an IssueModel of the issue to check view permission for
  516.      * @param userId      the userId of the user to check permission for
  517.      * @param permissions a HashMap of the users permissions
  518.      */
  519.     public static boolean canViewIssue(Issue issue, Integer userId,
  520.                                        Map<Integer, Set<PermissionType>> permissions) {

  521.         if (issue == null || userId == null || permissions == null) {
  522.             if (log.isInfoEnabled()) {
  523.                 log.info("canViewIssue: missing argument. issue: " + issue
  524.                         + ", userid: " + userId + ", permissions: "
  525.                         + permissions);
  526.             }
  527.             return false;
  528.         }

  529.         if (UserUtilities.hasPermission(permissions,
  530.                 issue.getProject().getId(), PermissionType.ISSUE_VIEW_ALL)) {
  531.             if (log.isDebugEnabled()) {
  532.                 log.debug("canViewIssue: issue: " + issue + ", user: " + userId
  533.                         + ", permission: " + PermissionType.ISSUE_VIEW_ALL);
  534.             }
  535.             return true;
  536.         }

  537.         boolean isUsersIssue = false;
  538.         // I think owner & creator should always be able to view the issue
  539.         // otherwise it makes no sense of creating the issue itself.
  540.         // So put these checks before checking permissions for the whole project.
  541.         if (issue.getCreator().getId().equals(userId)) {
  542.             if (log.isDebugEnabled()) {
  543.                 log.debug("canViewIssue: issue: " + issue + ", user: " + userId
  544.                         + ", permission: is creator");
  545.             }
  546.             isUsersIssue = true;
  547.         }

  548.         if (issue.getOwner() != null) {
  549.             if (issue.getOwner().getId().equals(userId)) {

  550.                 if (log.isDebugEnabled()) {
  551.                     log.debug("canViewIssue: issue: " + issue + ", user: "
  552.                             + userId + ", permission: is owner");
  553.                 }
  554.                 isUsersIssue = true;
  555.             }
  556.         }

  557.         // TODO should be checking for
  558.         // UserUtilities.hasPermission(permissions, issue.getProject()
  559.         //             .getId(), PermissionType.ISSUE_VIEW_USERS)
  560.         if (isUsersIssue) {
  561.             if (log.isDebugEnabled()) {
  562.                 log.debug("canViewIssue: issue: " + issue + ", user: "
  563.                         + userId + ", permission: isUsersIssue");
  564.             }
  565.             return true;
  566.         }


  567.         if (log.isDebugEnabled()) {
  568.             log.debug("canViewIssue: issue: " + issue + ", user: " + userId
  569.                     + ", permission: none matched");
  570.         }
  571.         return false;
  572.     }

  573.     /**
  574.      * Returns true if the user has permission to edit the requested issue.
  575.      *
  576.      * @param issue       an IssueModel of the issue to check edit permission for
  577.      * @param userId      the userId of the user to check permission for
  578.      * @param permissions a HashMap of the users permissions
  579.      */
  580.     @Deprecated
  581.     public static boolean canEditIssue(Issue issue, Integer userId,
  582.                                        Map<Integer, Set<PermissionType>> permissions) {
  583.         if (issue == null || userId == null || permissions == null) {
  584.             if (log.isInfoEnabled()) {
  585.                 log.info("canEditIssue: missing argument. issue: " + issue
  586.                         + ", userid: " + userId + ", permissions: "
  587.                         + permissions);
  588.             }
  589.             return false;
  590.         }


  591.         if (UserUtilities.hasPermission(permissions,
  592.                 issue.getProject().getId(), PermissionType.ISSUE_EDIT_ALL)) {

  593.             if (log.isDebugEnabled()) {
  594.                 log.debug("canEditIssue: user " + userId
  595.                         + " has permission to edit issue " + issue.getId()
  596.                         + ":" + PermissionType.ISSUE_EDIT_ALL);
  597.             }
  598.             return true;
  599.         }
  600.         if (!UserUtilities.hasPermission(permissions, issue.getProject()
  601.                 .getId(), PermissionType.ISSUE_EDIT_USERS)) {
  602.             if (log.isDebugEnabled()) {
  603.                 log.debug("canEditIssue: user " + userId
  604.                         + " has not permission  to edit issue " + issue.getId()
  605.                         + ":" + PermissionType.ISSUE_EDIT_USERS);
  606.             }
  607.             return false;
  608.         }

  609.         if (issue.getCreator().getId().equals(userId)) {
  610.             if (log.isDebugEnabled()) {
  611.                 log.debug("canEditIssue: user " + userId
  612.                         + " is creator of issue " + issue.getId() + ":");
  613.             }
  614.             return true;
  615.         }
  616.         if (issue.getOwner() != null) {
  617.             if (issue.getOwner().getId().equals(userId)) {
  618.                 if (log.isDebugEnabled()) {
  619.                     log.debug("canEditIssue: user " + userId
  620.                             + " is owner of issue " + issue.getId() + ":");
  621.                 }
  622.                 return true;
  623.             }
  624.         }

  625.         if (log.isDebugEnabled()) {
  626.             log.debug("canEditIssue: user " + userId
  627.                     + " could not match permission, denied");
  628.         }
  629.         return false;
  630.     }

  631.     /**
  632.      * Returns true if the user can be assigned to this issue.
  633.      *
  634.      * @param issue       an IssueModel of the issue to check assign permission for
  635.      * @param userId      the userId of the user to check permission for
  636.      * @param permissions a HashMap of the users permissions
  637.      */
  638.     @Deprecated
  639.     public static boolean canBeAssignedIssue(Issue issue, Integer userId,
  640.                                              Map<Integer, Set<PermissionType>> permissions) {
  641.         if (issue == null || userId == null || permissions == null) {
  642.             return false;
  643.         }

  644.         if (UserUtilities.hasPermission(permissions,
  645.                 issue.getProject().getId(), PermissionType.ISSUE_EDIT_ALL)) {
  646.             return true;
  647.         }
  648.         if (UserUtilities.hasPermission(permissions,
  649.                 issue.getProject().getId(), PermissionType.ISSUE_EDIT_USERS)) {
  650.             if (issue.getCreator().getId().equals(userId)) {
  651.                 return true;
  652.             } else if (UserUtilities.hasPermission(permissions, issue
  653.                     .getProject().getId(), PermissionType.ISSUE_ASSIGNABLE)) {
  654.                 return true;
  655.             } else if (issue.getOwner().getId() != null
  656.                     && issue.getOwner().getId().equals(userId)) {
  657.                 return true;
  658.             }
  659.         }

  660.         return false;
  661.     }

  662.     /**
  663.      * Returns true if the user can unassign themselves from the issue.
  664.      *
  665.      * @param issue       an IssueModel of the issue to check assign permission for
  666.      * @param userId      the userId of the user to check permission for
  667.      * @param permissions a HashMap of the users permissions
  668.      */
  669.     public static boolean canUnassignIssue(Issue issue, Integer userId,
  670.                                            Map<Integer, Set<PermissionType>> permissions) {
  671.         if (issue == null || userId == null || permissions == null) {
  672.             return false;
  673.         }

  674.         if (UserUtilities.hasPermission(permissions,
  675.                 issue.getProject().getId(), PermissionType.ISSUE_ASSIGN_OTHERS)) {
  676.             return true;
  677.         }
  678.         return issue.getOwner() != null
  679.                 && userId.equals(issue.getOwner().getId())
  680.                 && UserUtilities.hasPermission(permissions, issue.getProject()
  681.                 .getId(), PermissionType.ISSUE_UNASSIGN_SELF);

  682.     }

  683.     public static boolean hasIssueRelation(Issue issue, Integer relatedIssueId) {
  684.         if (issue != null) {
  685.             List<IssueRelation> relations = issue.getRelations();
  686.             for (IssueRelation relation : relations) {
  687.                 if (relation.getRelatedIssue().getId().equals(
  688.                         relatedIssueId)) {
  689.                     return true;
  690.                 }
  691.             }
  692.         }
  693.         return false;
  694.     }

  695.     public static boolean hasIssueNotification(Issue issue, Integer userId) {
  696.         return hasIssueNotification(issue, issue.getProject(), userId);
  697.     }

  698.     public static boolean hasHardNotification(Issue issue, Project project, Integer userId) {
  699.         if (issue == null || userId == null) {
  700.             return false;
  701.         }


  702.         if ((issue.getOwner() != null && issue.getOwner().getId().equals(userId))
  703.                 || issue.getCreator().getId().equals(userId)) {
  704.             return true;
  705.         }

  706.         if (project != null && project.getOwners() != null) {
  707.             for (User user : project.getOwners()) {
  708.                 if (user.getId().equals(userId)) {
  709.                     return true;
  710.                 }
  711.             }
  712.         }
  713.         Collection<Notification> notifications = issue.getNotifications();
  714.         if (notifications != null) {
  715.             for (Notification notification : notifications) {
  716.                 if (notification.getUser().getId().equals(userId) && notification.getRole() != Notification.Role.IP) {
  717.                     return true;
  718.                 }
  719.             }
  720.         }
  721.         return false;
  722.     }
  723.     /**
  724.      * Evaluate if a certain user is notified on issue change.
  725.      * <p/>
  726.      * FIXME: Does not work for admin of unassigned-issue-projects owner, see portalhome.do
  727.      */
  728.     public static boolean hasIssueNotification(Issue issue, Project project,
  729.                                                Integer userId) {
  730.         if (issue == null || userId == null) {
  731.             return false;
  732.         }

  733.         if (hasHardNotification(issue, project, userId)) {
  734.             return true;
  735.         }

  736.         Collection<Notification> notifications = issue.getNotifications();
  737.         if (notifications != null) {
  738.             for (Notification notification : notifications) {
  739.                 if (notification.getUser().getId().equals(userId)) {
  740.                     return true;
  741.                 }
  742.             }
  743.         }

  744.         return false;
  745.     }

  746.     public static URL getIssueURL(Issue issue, String baseURL) throws MalformedURLException {
  747.         return getIssueURL(issue, new URL(baseURL + (StringUtils.endsWith(baseURL, "/") ? "" : "/")
  748.         ));
  749.     }

  750.     public static URL getIssueURL(Issue issue, URL base) {
  751.         try {
  752.             if (null != base && null != issue)
  753.                 return new URL(base, "module-projects/view_issue.do?id=" + issue.getId());
  754.         } catch (MalformedURLException e) {
  755.             log.error("could not create URL", e);
  756.         }
  757.         return null;
  758.     }
  759. }