IssueServiceImpl.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.services.implementations;

  19. import org.apache.log4j.Logger;
  20. import org.itracker.IssueSearchException;
  21. import org.itracker.ProjectException;
  22. import org.itracker.core.resources.ITrackerResources;
  23. import org.itracker.model.*;
  24. import org.itracker.model.Notification.Role;
  25. import org.itracker.model.Notification.Type;
  26. import org.itracker.model.util.IssueUtilities;
  27. import org.itracker.persistence.dao.*;
  28. import org.itracker.services.ConfigurationService;
  29. import org.itracker.services.IssueService;
  30. import org.itracker.services.NotificationService;

  31. import java.util.*;

  32. /**
  33.  * Issue related service layer. A bit "fat" at this time, because of being a* direct EJB porting. Going go get thinner over time** @author ricardo
  34.  * */

  35. //TODO: Cleanup this file, go through all issues, todos, etc.

  36. public class IssueServiceImpl implements IssueService {

  37.     private static final Logger logger = Logger
  38.             .getLogger(IssueServiceImpl.class);
  39.     private ConfigurationService configurationService;

  40.     private CustomFieldDAO customFieldDAO;

  41.     private ProjectDAO projectDAO;

  42.     private IssueDAO issueDAO;

  43.     private IssueHistoryDAO issueHistoryDAO;

  44.     private IssueRelationDAO issueRelationDAO;

  45.     private IssueAttachmentDAO issueAttachmentDAO;

  46.     private ComponentDAO componentDAO;

  47.     private IssueActivityDAO issueActivityDAO;

  48.     private VersionDAO versionDAO;

  49.     private NotificationService notificationService;
  50.     private UserDAO userDAO;

  51.     public IssueServiceImpl() {

  52.     }

  53.     public Issue getIssue(Integer issueId) {
  54.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  55.         return issue;
  56.     }

  57.     /**
  58.      * @deprecated don't use to expensive memory use!
  59.      */
  60.     public List<Issue> getAllIssues() {
  61.         logger.warn("getAllIssues: use of deprecated API");
  62.         if (logger.isDebugEnabled()) {
  63.             logger
  64.                     .debug("getAllIssues: stacktrace was",
  65.                             new RuntimeException());
  66.         }
  67.         return getIssueDAO().findAll();
  68.     }

  69.     /**
  70.      * Added implementation to make proper count of ALL issues, instead select
  71.      * them in a list and return its size
  72.      */
  73.     public Long getNumberIssues() {
  74.         return getIssueDAO().countAllIssues();
  75.     }

  76.     public List<Issue> getIssuesCreatedByUser(Integer userId) {
  77.         return getIssuesCreatedByUser(userId, true);
  78.     }

  79.     public List<Issue> getIssuesCreatedByUser(Integer userId,
  80.                                               boolean availableProjectsOnly) {
  81.         final List<Issue> issues;

  82.         if (availableProjectsOnly) {
  83.             issues = getIssueDAO().findByCreatorInAvailableProjects(userId,
  84.                     IssueUtilities.STATUS_CLOSED);
  85.         } else {
  86.             issues = getIssueDAO().findByCreator(userId,
  87.                     IssueUtilities.STATUS_CLOSED);
  88.         }
  89.         return issues;
  90.     }

  91.     public List<Issue> getIssuesOwnedByUser(Integer userId) {

  92.         return getIssuesOwnedByUser(userId, true);

  93.     }

  94.     public List<Issue> getIssuesOwnedByUser(Integer userId,
  95.                                             boolean availableProjectsOnly) {
  96.         final List<Issue> issues;

  97.         if (availableProjectsOnly) {
  98.             issues = getIssueDAO().findByOwnerInAvailableProjects(userId,
  99.                     IssueUtilities.STATUS_RESOLVED);
  100.         } else {
  101.             issues = getIssueDAO().findByOwner(userId,
  102.                     IssueUtilities.STATUS_RESOLVED);
  103.         }
  104.         return issues;
  105.     }

  106.     public List<Issue> getIssuesWatchedByUser(Integer userId) {
  107.         return getIssuesWatchedByUser(userId, true);
  108.     }

  109.     /**
  110.      * TODO move to {@link NotificationService}
  111.      */
  112.     public List<Issue> getIssuesWatchedByUser(Integer userId,
  113.                                               boolean availableProjectsOnly) {
  114.         final List<Issue> issues;

  115.         if (availableProjectsOnly) {
  116.             issues = getIssueDAO().findByNotificationInAvailableProjects(
  117.                     userId, IssueUtilities.STATUS_CLOSED);
  118.         } else {
  119.             issues = getIssueDAO().findByNotification(userId,
  120.                     IssueUtilities.STATUS_CLOSED);
  121.         }
  122.         return issues;
  123.     }

  124.     public List<Issue> getUnassignedIssues() {
  125.         return getUnassignedIssues(true);
  126.     }

  127.     public List<Issue> getUnassignedIssues(boolean availableProjectsOnly) {
  128.         final List<Issue> issues;

  129.         if (availableProjectsOnly) {
  130.             issues = getIssueDAO()
  131.                     .findByStatusLessThanEqualToInAvailableProjects(
  132.                             IssueUtilities.STATUS_UNASSIGNED);
  133.         } else {
  134.             issues = getIssueDAO().findByStatusLessThanEqualTo(
  135.                     IssueUtilities.STATUS_UNASSIGNED);
  136.         }
  137.         return issues;
  138.     }

  139.     /**
  140.      * Returns all issues with a status equal to the given status number
  141.      *
  142.      * @param status the status to compare
  143.      * @return an array of IssueModels that match the criteria
  144.      */

  145.     public List<Issue> getIssuesWithStatus(int status) {
  146.         List<Issue> issues = getIssueDAO().findByStatus(status);
  147.         return issues;
  148.     }

  149.     /**
  150.      * Returns all issues with a status less than the given status number
  151.      *
  152.      * @param status the status to compare
  153.      * @return an array of IssueModels that match the criteria
  154.      */

  155.     public List<Issue> getIssuesWithStatusLessThan(int status) {
  156.         List<Issue> issues = getIssueDAO().findByStatusLessThan(status);
  157.         return issues;
  158.     }

  159.     /**
  160.      * Returns all issues with a severity equal to the given severity number
  161.      *
  162.      * @param severity the severity to compare
  163.      * @return an array of IssueModels that match the criteria
  164.      */

  165.     public List<Issue> getIssuesWithSeverity(int severity) {
  166.         List<Issue> issues = getIssueDAO().findBySeverity(severity);
  167.         return issues;

  168.     }

  169.     public List<Issue> getIssuesByProjectId(Integer projectId) {
  170.         return getIssuesByProjectId(projectId, IssueUtilities.STATUS_END);
  171.     }

  172.     public List<Issue> getIssuesByProjectId(Integer projectId, int status) {
  173.         List<Issue> issues = getIssueDAO().findByProjectAndLowerStatus(
  174.                 projectId, status);
  175.         return issues;
  176.     }

  177.     public User getIssueCreator(Integer issueId) {
  178.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  179.         User user = issue.getCreator();
  180.         return user;

  181.     }

  182.     public User getIssueOwner(Integer issueId) {
  183.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  184.         User user = issue.getOwner();

  185.         return user;

  186.     }

  187.     public List<Component> getIssueComponents(Integer issueId) {
  188.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  189.         List<Component> components = issue.getComponents();

  190.         return components;
  191.     }

  192.     public List<Version> getIssueVersions(Integer issueId) {
  193.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  194.         List<Version> versions = issue.getVersions();
  195.         return versions;
  196.     }

  197.     public List<IssueAttachment> getIssueAttachments(Integer issueId) {
  198.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  199.         List<IssueAttachment> attachments = issue.getAttachments();
  200.         return attachments;
  201.     }

  202.     /**
  203.      * Old implementation is left here, commented, because it checked for
  204.      * history entry status. This feature was not finished, I think (RJST)
  205.      */
  206.     public List<IssueHistory> getIssueHistory(Integer issueId) {
  207.         return getIssueDAO().findByPrimaryKey(issueId).getHistory();
  208.     }

  209.     public Issue createIssue(Issue issue, Integer projectId, Integer userId,
  210.                              Integer createdById) throws ProjectException {
  211.         Project project = getProjectDAO().findByPrimaryKey(projectId);
  212.         User creator = getUserDAO().findByPrimaryKey(userId);

  213.         if (project.getStatus() != Status.ACTIVE) {
  214.             throw new ProjectException("Project is not active.");
  215.         }

  216.         IssueActivity activity = new IssueActivity(issue, creator,
  217.                 IssueActivityType.ISSUE_CREATED);
  218.         activity.setDescription(ITrackerResources
  219.                 .getString("itracker.activity.system.createdfor")
  220.                 + " " + creator.getFirstName() + " " + creator.getLastName());

  221.         activity.setIssue(issue);

  222.         if (!(createdById == null || createdById.equals(userId))) {

  223.             User createdBy = getUserDAO().findByPrimaryKey(createdById);
  224.             activity.setUser(createdBy);

  225.             Notification watchModel = new Notification();

  226.             watchModel.setUser(createdBy);

  227.             watchModel.setIssue(issue);

  228.             watchModel.setRole(Notification.Role.CONTRIBUTER);

  229.             issue.getNotifications().add(watchModel);

  230.         }

  231.         List<IssueActivity> activities = new ArrayList<IssueActivity>();
  232.         activities.add(activity);
  233.         issue.setActivities(activities);

  234.         issue.setProject(project);

  235.         issue.setCreator(creator);

  236.         // save
  237.         getIssueDAO().save(issue);

  238.         return issue;
  239.     }

  240.     /**
  241.      * Save a modified issue to the persistence layer
  242.      *
  243.      * @param issueDirty the changed, unsaved issue to update on persistency layer
  244.      * @param userId     the user-id of the changer
  245.      */
  246.     public Issue updateIssue(final Issue issueDirty, final Integer userId)
  247.             throws ProjectException {

  248.         String existingTargetVersion = null;

  249.         // detach the modified Issue form the Hibernate Session
  250.         getIssueDAO().detach(issueDirty);
  251.         // Retrieve the Issue from Hibernate Session and refresh it from
  252.         // Hibernate Session to previous state.
  253.         Issue persistedIssue = getIssueDAO().findByPrimaryKey(
  254.                 issueDirty.getId());

  255.         getIssueDAO().refresh(persistedIssue);
  256.         if (logger.isDebugEnabled()) {
  257.             logger.debug("updateIssue: updating issue " + issueDirty
  258.                     + "\n(from " + persistedIssue + ")");
  259.         }

  260.         User user = getUserDAO().findByPrimaryKey(userId);

  261.         if (persistedIssue.getProject().getStatus() != Status.ACTIVE) {
  262.             throw new ProjectException("Project "
  263.                     + persistedIssue.getProject().getName() + " is not active.");
  264.         }

  265.         if (!persistedIssue.getDescription().equalsIgnoreCase(
  266.                 issueDirty.getDescription())) {

  267.             if (logger.isDebugEnabled()) {
  268.                 logger.debug("updateIssue: updating description from "
  269.                         + persistedIssue.getDescription());
  270.             }
  271.             IssueActivity activity = new IssueActivity();
  272.             activity.setActivityType(IssueActivityType.DESCRIPTION_CHANGE);
  273.             activity.setDescription(ITrackerResources
  274.                     .getString("itracker.web.generic.from")
  275.                     + ": " + persistedIssue.getDescription());
  276.             activity.setUser(user);
  277.             activity.setIssue(issueDirty);
  278.             issueDirty.getActivities().add(activity);

  279.         }

  280.         if (persistedIssue.getResolution() != null
  281.                 && !persistedIssue.getResolution().equalsIgnoreCase(
  282.                 issueDirty.getResolution())) {

  283.             IssueActivity activity = new IssueActivity();
  284.             activity.setActivityType(IssueActivityType.RESOLUTION_CHANGE);
  285.             activity.setDescription(ITrackerResources
  286.                     .getString("itracker.web.generic.from")
  287.                     + ": " + persistedIssue.getResolution());
  288.             activity.setUser(user);
  289.             activity.setIssue(issueDirty);
  290.             issueDirty.getActivities().add(activity);
  291.         }

  292.         if (null == persistedIssue.getStatus()
  293.                 || !persistedIssue.getStatus().equals(issueDirty.getStatus())) {
  294.             IssueActivity activity = new IssueActivity();
  295.             activity.setActivityType(IssueActivityType.STATUS_CHANGE);
  296.             activity.setDescription(IssueUtilities.getStatusName(persistedIssue
  297.                     .getStatus())
  298.                     + " "
  299.                     + ITrackerResources.getString("itracker.web.generic.to")
  300.                     + " "
  301.                     + IssueUtilities.getStatusName(issueDirty.getStatus()));
  302.             activity.setUser(user);
  303.             activity.setIssue(issueDirty);
  304.             issueDirty.getActivities().add(activity);
  305.         }

  306.         if (issueDirty.getSeverity() != null
  307.                 && !issueDirty.getSeverity().equals(
  308.                 persistedIssue.getSeverity())
  309.                 && issueDirty.getSeverity() != -1) {

  310.             IssueActivity activity = new IssueActivity();
  311.             activity.setActivityType(IssueActivityType.SEVERITY_CHANGE);
  312.             // FIXME why does it state Critical to Critical when it should Major to Critical!?
  313.             activity.setDescription(IssueUtilities
  314.                     .getSeverityName(persistedIssue.getSeverity())
  315.                     + " "
  316.                     + ITrackerResources.getString("itracker.web.generic.to")
  317.                     + " "
  318.                     + IssueUtilities.getSeverityName(issueDirty.getSeverity()));

  319.             activity.setUser(user);
  320.             activity.setIssue(issueDirty);
  321.             issueDirty.getActivities().add(activity);
  322.         }

  323.         if (persistedIssue.getTargetVersion() != null
  324.                 && issueDirty.getTargetVersion() != null
  325.                 && !persistedIssue.getTargetVersion().getId().equals(
  326.                 issueDirty.getTargetVersion().getId())) {
  327.             existingTargetVersion = persistedIssue.getTargetVersion()
  328.                     .getNumber();
  329.             Version version = this.getVersionDAO().findByPrimaryKey(
  330.                     issueDirty.getTargetVersion().getId());

  331.             IssueActivity activity = new IssueActivity();
  332.             activity.setActivityType(IssueActivityType.TARGETVERSION_CHANGE);
  333.             String description = existingTargetVersion + " "
  334.                     + ITrackerResources.getString("itracker.web.generic.to")
  335.                     + " ";
  336.             description += version.getNumber();
  337.             activity.setDescription(description);
  338.             activity.setUser(user);
  339.             activity.setIssue(issueDirty);
  340.             issueDirty.getActivities().add(activity);
  341.         }

  342.         // (re-)assign issue
  343.         User newOwner = issueDirty.getOwner();
  344.         issueDirty.setOwner(persistedIssue.getOwner());
  345.         if (logger.isDebugEnabled()) {
  346.             logger.debug("updateIssue: assigning from " + issueDirty.getOwner()
  347.                     + " to " + newOwner);
  348.         }
  349.         assignIssue(issueDirty, newOwner, user, false);
  350.         if (logger.isDebugEnabled()) {
  351.             logger.debug("updateIssue: updated assignment: " + issueDirty);
  352.         }

  353.         if (logger.isDebugEnabled()) {
  354.             logger.debug("updateIssue: merging issue " + issueDirty + " to "
  355.                     + persistedIssue);
  356.         }

  357.         persistedIssue = getIssueDAO().merge(issueDirty);

  358.         if (logger.isDebugEnabled()) {
  359.             logger.debug("updateIssue: merged issue for saving: "
  360.                     + persistedIssue);
  361.         }
  362.         getIssueDAO().saveOrUpdate(persistedIssue);
  363.         if (logger.isDebugEnabled()) {
  364.             logger.debug("updateIssue: saved issue: " + persistedIssue);
  365.         }
  366.         return persistedIssue;
  367.     }

  368.     /**
  369.      * Moves an issues from its current project to a new project.
  370.      *
  371.      * @param issue     an Issue of the issue to move
  372.      * @param projectId the id of the target project
  373.      * @param userId    the id of the user that is moving the issue
  374.      * @return an Issue of the issue after it has been moved
  375.      */

  376.     public Issue moveIssue(Issue issue, Integer projectId, Integer userId) {

  377.         if (logger.isDebugEnabled()) {
  378.             logger.debug("moveIssue: " + issue + " to project#" + projectId
  379.                     + ", user#" + userId);
  380.         }

  381.         Project project = getProjectDAO().findByPrimaryKey(projectId);
  382.         User user = getUserDAO().findByPrimaryKey(userId);

  383.         if (logger.isDebugEnabled()) {
  384.             logger.debug("moveIssue: " + issue + " to project: " + project
  385.                     + ", user: " + user);
  386.         }

  387.         IssueActivity activity = new IssueActivity();
  388.         activity
  389.                 .setActivityType(org.itracker.model.IssueActivityType.ISSUE_MOVE);
  390.         activity.setDescription(issue.getProject().getName() + " "
  391.                 + ITrackerResources.getString("itracker.web.generic.to") + " "
  392.                 + project.getName());
  393.         activity.setUser(user);
  394.         activity.setIssue(issue);
  395.         issue.setProject(project);

  396.         issue.getActivities().add(activity);

  397.         if (logger.isDebugEnabled()) {
  398.             logger.debug("moveIssue: updated issue: " + issue);
  399.         }
  400.         try {
  401.             getIssueDAO().saveOrUpdate(issue);
  402.         } catch (Exception e) {
  403.             logger.error("moveIssue: failed to save issue: " + issue, e);
  404.             return null;
  405.         }
  406.         if (logger.isDebugEnabled()) {
  407.             logger.debug("moveIssue: saved move-issue to " + project);
  408.         }
  409.         return issue;

  410.     }

  411.     /**
  412.      * this should not exist. adding an history entry should be adding the
  413.      * history entry to the domain object and saving the object...
  414.      */
  415.     public boolean addIssueHistory(IssueHistory history) {
  416.         getIssueHistoryDAO().saveOrUpdate(history);
  417.         history.getIssue().getHistory().add(history);
  418.         getIssueDAO().saveOrUpdate(history.getIssue());
  419.         return true;
  420.     }

  421.     /**
  422.      * TODO maybe it has no use at all. is it obsolete? when I'd set the
  423.      * issue-fields on an issue and then save/update issue, would it be good
  424.      * enough?
  425.      */
  426.     public boolean setIssueFields(Integer issueId, List<IssueField> fields) {
  427.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  428.         setIssueFields(issue, fields, true);

  429.         return true;
  430.     }

  431.     private boolean setIssueFields(Issue issue, List<IssueField> fields,
  432.                                    boolean save) {

  433.         List<IssueField> issueFields = issue.getFields();

  434.         if (fields.size() > 0) {
  435.             for (int i = 0; i < fields.size(); i++) {

  436.                 IssueField field = fields.get(i);
  437.                 if (issueFields.contains(field)) {
  438.                     issueFields.remove(field);
  439.                 }

  440.                 CustomField customField = getCustomFieldDAO().findByPrimaryKey(
  441.                         fields.get(i).getCustomField().getId());
  442.                 field.setCustomField(customField);
  443.                 field.setIssue(issue);

  444.                 issueFields.add(field);
  445.             }
  446.         }
  447.         issue.setFields(issueFields);

  448.         if (save) {
  449.             logger.debug("setIssueFields: save was true");
  450.             getIssueDAO().saveOrUpdate(issue);
  451.         }
  452.         return true;
  453.     }

  454.     public boolean setIssueComponents(Integer issueId,
  455.                                       HashSet<Integer> componentIds, Integer userId) {

  456.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  457.         List<Component> components = new ArrayList<Component>(componentIds
  458.                 .size());
  459.         User user = userDAO.findByPrimaryKey(userId);
  460.         Iterator<Integer> idIt = componentIds.iterator();
  461.         while (idIt.hasNext()) {
  462.             Integer id = (Integer) idIt.next();
  463.             Component c = getComponentDAO().findById(id);
  464.             components.add(c);
  465.         }

  466.         setIssueComponents(issue, components, user, true);
  467.         return true;
  468.     }

  469.     private boolean setIssueComponents(Issue issue, List<Component> components,
  470.                                        User user, boolean save) {

  471.         if (issue.getComponents() == null) {
  472.             if (logger.isInfoEnabled()) {
  473.                 logger.info("setIssueComponents: components was null");
  474.             }
  475.             issue.setComponents(new ArrayList<Component>(components.size()));
  476.         }
  477.         if (components.isEmpty() && !issue.getComponents().isEmpty()) {
  478.             addComponentsModifiedActivity(issue, user, new StringBuilder(
  479.                     ITrackerResources.getString("itracker.web.generic.all"))
  480.                     .append(" ").append(
  481.                             ITrackerResources
  482.                                     .getString("itracker.web.generic.removed"))
  483.                     .toString());
  484.             issue.getComponents().clear();
  485.         } else {
  486.             Collections.sort(issue.getComponents(), Component.NAME_COMPARATOR);

  487.             for (Iterator<Component> iterator = issue.getComponents()
  488.                     .iterator(); iterator.hasNext(); ) {
  489.                 Component component = (Component) iterator.next();
  490.                 if (components.contains(component)) {
  491.                     components.remove(component);
  492.                 } else {
  493.                     addComponentsModifiedActivity(issue, user,
  494.                             new StringBuilder(ITrackerResources
  495.                                     .getString("itracker.web.generic.removed"))
  496.                                     .append(": ").append(component.getName())
  497.                                     .toString());
  498.                     iterator.remove();
  499.                 }
  500.             }
  501.             Collections.sort(components, Component.NAME_COMPARATOR);
  502.             for (Iterator<Component> iterator = components.iterator(); iterator
  503.                     .hasNext(); ) {

  504.                 Component component = iterator.next();
  505.                 if (!issue.getComponents().contains(component)) {
  506.                     addComponentsModifiedActivity(issue, user,
  507.                             new StringBuilder(ITrackerResources
  508.                                     .getString("itracker.web.generic.added"))
  509.                                     .append(": ").append(component.getName())
  510.                                     .toString());
  511.                     issue.getComponents().add(component);
  512.                 }
  513.             }
  514.         }

  515.         if (save) {
  516.             if (logger.isDebugEnabled()) {
  517.                 logger.debug("setIssueComponents: save was true");
  518.             }
  519.             getIssueDAO().saveOrUpdate(issue);
  520.         }
  521.         return true;

  522.     }

  523.     /**
  524.      * used by setIssueComponents for adding change activities
  525.      */
  526.     private void addComponentsModifiedActivity(Issue issue, User user,
  527.                                                String description) {
  528.         IssueActivity activity = new IssueActivity();
  529.         activity
  530.                 .setActivityType(org.itracker.model.IssueActivityType.COMPONENTS_MODIFIED);
  531.         activity.setDescription(description);
  532.         activity.setIssue(issue);
  533.         activity.setUser(user);
  534.         issue.getActivities().add(activity);
  535.     }

  536.     private boolean setIssueVersions(Issue issue, List<Version> versions,
  537.                                      User user, boolean save) {

  538.         if (issue.getVersions() == null) {
  539.             if (logger.isInfoEnabled()) {
  540.                 logger.info("setIssueVersions: versions were null!");
  541.             }
  542.             issue.setVersions(new ArrayList<Version>());
  543.         }

  544.         if (versions.isEmpty() && !issue.getVersions().isEmpty()) {

  545.             addVersionsModifiedActivity(issue, user, new StringBuilder(
  546.                     ITrackerResources.getString("itracker.web.generic.all"))
  547.                     .append(" ").append(
  548.                             ITrackerResources
  549.                                     .getString("itracker.web.generic.removed"))
  550.                     .toString());
  551.             issue.getVersions().clear();
  552.         } else {

  553.             Collections.sort(issue.getVersions(), Version.VERSION_COMPARATOR);

  554.             StringBuilder changesBuf = new StringBuilder();
  555.             for (Iterator<Version> iterator = issue.getVersions().iterator(); iterator
  556.                     .hasNext(); ) {

  557.                 Version version = iterator.next();
  558.                 if (versions.contains(version)) {
  559.                     versions.remove(version);
  560.                 } else {
  561.                     if (changesBuf.length() > 0) {
  562.                         changesBuf.append(", ");
  563.                     }
  564.                     changesBuf.append(version.getNumber());
  565.                     iterator.remove();
  566.                 }
  567.             }

  568.             if (changesBuf.length() > 0) {
  569.                 addVersionsModifiedActivity(issue, user, new StringBuilder(
  570.                         ITrackerResources
  571.                                 .getString("itracker.web.generic.removed"))
  572.                         .append(": ").append(changesBuf).toString());
  573.             }

  574.             changesBuf = new StringBuilder();

  575.             Collections.sort(versions, Version.VERSION_COMPARATOR);
  576.             for (Iterator<Version> iterator = versions.iterator(); iterator
  577.                     .hasNext(); ) {

  578.                 Version version = iterator.next();
  579.                 if (changesBuf.length() > 0) {
  580.                     changesBuf.append(", ");
  581.                 }
  582.                 changesBuf.append(version.getNumber());
  583.                 issue.getVersions().add(version);
  584.             }
  585.             if (changesBuf.length() > 0) {
  586.                 addVersionsModifiedActivity(issue, user, new StringBuilder(
  587.                         ITrackerResources
  588.                                 .getString("itracker.web.generic.added"))
  589.                         .append(": ").append(changesBuf).toString());
  590.             }
  591.         }
  592.         if (save) {
  593.             if (logger.isDebugEnabled()) {
  594.                 logger.debug("setIssueVersions: updating issue: " + issue);
  595.             }
  596.             getIssueDAO().saveOrUpdate(issue);
  597.         }

  598.         return true;
  599.     }

  600.     /**
  601.      * used by setIssueComponents for adding change activities
  602.      */
  603.     private void addVersionsModifiedActivity(Issue issue, User user,
  604.                                              String description) {
  605.         IssueActivity activity = new IssueActivity();
  606.         activity
  607.                 .setActivityType(org.itracker.model.IssueActivityType.TARGETVERSION_CHANGE);
  608.         activity.setDescription(description);
  609.         activity.setIssue(issue);
  610.         activity.setUser(user);
  611.         issue.getActivities().add(activity);
  612.     }

  613.     public boolean setIssueVersions(Integer issueId,
  614.                                     HashSet<Integer> versionIds, Integer userId) {

  615.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  616.         User user = userDAO.findByPrimaryKey(userId);
  617.         // load versions from ids
  618.         ArrayList<Version> versions = new ArrayList<Version>(versionIds.size());
  619.         Iterator<Integer> versionsIdIt = versionIds.iterator();
  620.         while (versionsIdIt.hasNext()) {
  621.             Integer id = versionsIdIt.next();
  622.             versions.add(getVersionDAO().findByPrimaryKey(id));
  623.         }

  624.         return setIssueVersions(issue, versions, user, true);
  625.     }

  626.     public IssueRelation getIssueRelation(Integer relationId) {

  627.         IssueRelation issueRelation = getIssueRelationDAO().findByPrimaryKey(
  628.                 relationId);

  629.         return issueRelation;

  630.     }

  631.     /**
  632.      * add a relation between two issues.
  633.      * <p/>
  634.      * TODO: There is no relation saved to database yet?
  635.      */
  636.     public boolean addIssueRelation(Integer issueId, Integer relatedIssueId,
  637.                                     IssueRelation.Type relationType, Integer userId) {

  638.         User user = getUserDAO().findByPrimaryKey(userId);

  639.         if (null == user) {
  640.             throw new IllegalArgumentException("Invalid user-id: " + userId);
  641.         }

  642.         if (issueId != null && relatedIssueId != null) {

  643.             IssueRelation.Type matchingRelationType = IssueUtilities
  644.                     .getMatchingRelationType(relationType);

  645.             // if(matchingRelationType < 0) {

  646.             // throw new CreateException("Unable to find matching relation type

  647.             // for type: " + relationType);

  648.             // }

  649.             Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  650.             Issue relatedIssue = getIssueDAO().findByPrimaryKey(relatedIssueId);

  651.             IssueRelation relationA = new IssueRelation();

  652.             relationA.setRelationType(relationType);

  653.             // relationA.setMatchingRelationId(relationBId);

  654.             relationA.setIssue(issue);

  655.             relationA.setRelatedIssue(relatedIssue);

  656.             // set to 0 first, later reassign to relationB.id
  657.             relationA.setMatchingRelationId(0);

  658.             relationA.setLastModifiedDate(new java.sql.Timestamp(new Date()
  659.                     .getTime()));

  660.             getIssueRelationDAO().saveOrUpdate(relationA);

  661.             IssueRelation relationB = new IssueRelation();

  662.             relationB.setRelationType(matchingRelationType);

  663.             // relationB.setMatchingRelationId(relationAId);

  664.             relationB.setIssue(relatedIssue);

  665.             relationB.setRelatedIssue(issue);

  666.             relationB.setMatchingRelationId(relationA.getId());

  667.             relationB.setLastModifiedDate(new java.sql.Timestamp(new Date()
  668.                     .getTime()));

  669.             getIssueRelationDAO().saveOrUpdate(relationB);

  670.             relationA.setMatchingRelationId(relationB.getId());
  671.             getIssueRelationDAO().saveOrUpdate(relationA);

  672.             IssueActivity activity = new IssueActivity();
  673.             activity
  674.                     .setActivityType(org.itracker.model.IssueActivityType.RELATION_ADDED);
  675.             activity.setDescription(ITrackerResources.getString(
  676.                     "itracker.activity.relation.add", new Object[]{
  677.                     IssueUtilities.getRelationName(relationType),
  678.                     relatedIssueId}));

  679.             activity.setIssue(issue);
  680.             issue.getActivities().add(activity);
  681.             // need to set user here
  682.             activity.setUser(user);
  683.             // need to save here
  684.             getIssueDAO().saveOrUpdate(issue);

  685.             activity = new IssueActivity();
  686.             activity
  687.                     .setActivityType(org.itracker.model.IssueActivityType.RELATION_ADDED);
  688.             activity.setDescription(ITrackerResources.getString(
  689.                     "itracker.activity.relation.add", new Object[]{
  690.                     IssueUtilities
  691.                             .getRelationName(matchingRelationType),
  692.                     issueId}));
  693.             activity.setIssue(relatedIssue);
  694.             activity.setUser(user);
  695.             relatedIssue.getActivities().add(activity);
  696.             getIssueDAO().saveOrUpdate(relatedIssue);
  697.             return true;

  698.         }

  699.         return false;

  700.     }

  701.     public void removeIssueRelation(Integer relationId, Integer userId) {
  702.         IssueRelation issueRelation = getIssueRelationDAO().findByPrimaryKey(
  703.                 relationId);
  704.         Integer issueId = issueRelation.getIssue().getId();

  705.         Integer relatedIssueId = issueRelation.getRelatedIssue().getId();

  706.         Integer matchingRelationId = issueRelation.getMatchingRelationId();

  707.         if (matchingRelationId != null) {
  708.             IssueActivity activity = new IssueActivity();
  709.             activity
  710.                     .setActivityType(org.itracker.model.IssueActivityType.RELATION_REMOVED);
  711.             activity.setDescription(ITrackerResources.getString(
  712.                     "itracker.activity.relation.removed", issueId.toString()));
  713.             // FIXME need to fix the commented code and save
  714.             // activity.setIssue(relatedIssueId);
  715.             // activity.setUser(userId);
  716.             // IssueRelationDAO.remove(matchingRelationId);
  717.         }

  718.         IssueActivity activity = new IssueActivity();
  719.         activity
  720.                 .setActivityType(org.itracker.model.IssueActivityType.RELATION_REMOVED);
  721.         activity.setDescription(ITrackerResources
  722.                 .getString("itracker.activity.relation.removed", relatedIssueId
  723.                         .toString()));
  724.         // activity.setIssue(issueId);
  725.         // activity.setUser(userId);
  726.         // irHome.remove(relationId);
  727.         // need to save

  728.         getIssueRelationDAO().delete(issueRelation);
  729.     }

  730.     public boolean assignIssue(Integer issueId, Integer userId) {
  731.         return assignIssue(issueId, userId, userId);
  732.     }

  733.     /**
  734.      * only use for updating issue from actions..
  735.      */
  736.     public boolean assignIssue(Integer issueId, Integer userId,
  737.                                Integer assignedByUserId) {

  738.         return assignIssue(getIssueDAO().findByPrimaryKey(issueId),
  739.                 getUserDAO().findByPrimaryKey(userId), getUserDAO()
  740.                 .findByPrimaryKey(assignedByUserId), true);
  741.     }

  742.     /**
  743.      * Only for use
  744.      *
  745.      * @param save save issue and send notification
  746.      */
  747.     private boolean assignIssue(Issue issue, User user, User assignedByUser,
  748.                                 final boolean save) {

  749.         if (issue.getOwner() == user
  750.                 || (null != issue.getOwner() && issue.getOwner().equals(user))) {
  751.             // nothing to do.
  752.             if (logger.isDebugEnabled()) {
  753.                 logger.debug("assignIssue: attempted to reassign " + issue
  754.                         + " to current owner " + user);
  755.             }
  756.             return false;
  757.         }

  758.         if (null == user) {
  759.             if (logger.isInfoEnabled()) {
  760.                 logger.info("assignIssue: call to unasign " + issue);
  761.             }

  762.             return unassignIssue(issue, assignedByUser, save);
  763.         }

  764.         if (logger.isInfoEnabled()) {
  765.             logger.info("assignIssue: assigning " + issue + " to " + user);
  766.         }

  767.         User currOwner = issue.getOwner();

  768.         if (!user.equals(currOwner)) {
  769.             if (currOwner != null
  770.                     && !notificationService.hasIssueNotification(issue,
  771.                     currOwner.getId(), Role.IP)) {
  772.                 // Notification notification = new Notification();
  773.                 Notification notification = new Notification(currOwner, issue,
  774.                         Role.IP);
  775.                 if (save) {
  776.                     notificationService.addIssueNotification(notification);
  777.                 } else {
  778.                     issue.getNotifications().add(notification);
  779.                 }
  780.             }

  781.             IssueActivity activity = new IssueActivity();
  782.             activity
  783.                     .setActivityType(org.itracker.model.IssueActivityType.OWNER_CHANGE);
  784.             activity.setDescription((currOwner == null ? "["
  785.                     + ITrackerResources
  786.                     .getString("itracker.web.generic.unassigned") + "]"
  787.                     : currOwner.getLogin())
  788.                     + " "
  789.                     + ITrackerResources.getString("itracker.web.generic.to")
  790.                     + " " + user.getLogin());
  791.             activity.setUser(assignedByUser);
  792.             activity.setIssue(issue);
  793.             issue.getActivities().add(activity);

  794.             issue.setOwner(user);

  795.             if (logger.isDebugEnabled()) {
  796.                 logger.debug("assignIssue: current status: "
  797.                         + issue.getStatus());
  798.             }
  799.             if (issue.getStatus() < IssueUtilities.STATUS_ASSIGNED) {
  800.                 issue.setStatus(IssueUtilities.STATUS_ASSIGNED);
  801.                 if (logger.isDebugEnabled()) {
  802.                     logger.debug("assignIssue: new status set to "
  803.                             + issue.getStatus());
  804.                 }
  805.             }

  806.             // send assignment notification
  807.             if (save) {
  808.                 if (logger.isDebugEnabled()) {
  809.                     logger.debug("assignIssue: saving re-assigned issue");
  810.                 }
  811.                 getIssueDAO().saveOrUpdate(issue);
  812.                 notificationService.sendNotification(issue, Type.ASSIGNED,
  813.                         getConfigurationService().getSystemBaseURL());

  814.             }
  815.         }
  816.         return true;

  817.     }

  818.     /**
  819.      * @param save save issue and send notification
  820.      */
  821.     private boolean unassignIssue(Issue issue, User unassignedByUser,
  822.                                   boolean save) {
  823.         if (logger.isDebugEnabled()) {
  824.             logger.debug("unassignIssue: " + issue);
  825.         }
  826.         if (issue.getOwner() != null) {

  827.             if (logger.isDebugEnabled()) {
  828.                 logger.debug("unassignIssue: unassigning from "
  829.                         + issue.getOwner());
  830.             }
  831.             if (!notificationService.hasIssueNotification(issue, issue
  832.                     .getOwner().getId(), Role.CONTRIBUTER)) {
  833.                 // Notification notification = new Notification();
  834.                 Notification notification = new Notification(issue.getOwner(),
  835.                         issue, Role.CONTRIBUTER);
  836.                 if (save) {
  837.                     notificationService.addIssueNotification(notification);
  838.                 } else {
  839.                     issue.getNotifications().add(notification);
  840.                 }
  841.             }
  842.             IssueActivity activity = new IssueActivity(issue, unassignedByUser,
  843.                     IssueActivityType.OWNER_CHANGE);
  844.             activity
  845.                     .setDescription(issue.getOwner().getLogin()
  846.                             + " "
  847.                             + ITrackerResources
  848.                             .getString("itracker.web.generic.to")
  849.                             + " ["
  850.                             + ITrackerResources
  851.                             .getString("itracker.web.generic.unassigned")
  852.                             + "]");

  853.             issue.setOwner(null);

  854.             if (issue.getStatus() >= IssueUtilities.STATUS_ASSIGNED) {
  855.                 issue.setStatus(IssueUtilities.STATUS_UNASSIGNED);
  856.             }
  857.             if (save) {
  858.                 if (logger.isDebugEnabled()) {
  859.                     logger.debug("unassignIssue: saving unassigned issue..");
  860.                 }
  861.                 getIssueDAO().saveOrUpdate(issue);
  862.                 notificationService.sendNotification(issue, Type.ASSIGNED,
  863.                         getConfigurationService().getSystemBaseURL());
  864.             }
  865.         }

  866.         return true;
  867.     }

  868.     /**
  869.      * System-Update an issue, adds the action to the issue and updates the
  870.      * issue
  871.      */
  872.     public Issue systemUpdateIssue(Issue updateissue, Integer userId)
  873.             throws ProjectException {

  874.         IssueActivity activity = new IssueActivity();
  875.         activity.setActivityType(IssueActivityType.SYSTEM_UPDATE);
  876.         activity.setDescription(ITrackerResources
  877.                 .getString("itracker.activity.system.status"));
  878.         ArrayList<IssueActivity> activities = new ArrayList<IssueActivity>();

  879.         activity.setIssue(updateissue);
  880.         activity.setUser(getUserDAO().findByPrimaryKey(userId));
  881.         updateissue.getActivities().add(activity);

  882.         Issue updated = updateIssue(updateissue, userId);
  883.         updated.getActivities().addAll(activities);
  884.         getIssueDAO().saveOrUpdate(updated);

  885.         return updated;
  886.     }

  887.     /*
  888.       * public boolean addIssueActivity(IssueActivityModel model) {
  889.       *
  890.       * Issue issue = ifHome.findByPrimaryKey(model.getIssueId());
  891.       *
  892.       * User user = ufHome.findByPrimaryKey(model.getUserId());
  893.       *
  894.       * //return addIssueActivity(model, issue, user); return
  895.       * addIssueActivity(null, issue, user); }
  896.       */

  897.     /*
  898.       * public boolean addIssueActivity(IssueActivityModel model, Issue issue) {
  899.       *
  900.       * User user = ufHome.findByPrimaryKey(model.getUserId());
  901.       *
  902.       * return true;//addIssueActivity(model, issue, user); }
  903.       */

  904.     /**
  905.      * I think this entire method is useless - RJST TODO move to
  906.      * {@link NotificationService}
  907.      */
  908.     /*
  909.       * public boolean addIssueActivity(IssueActivityBean model, Issue issue,
  910.       * User user) {
  911.       *
  912.       * IssueActivityBean activity = new IssueActivityBean();
  913.       *
  914.       * //activity.setModel(model);
  915.       *
  916.       * activity.setIssue(issue);
  917.       *
  918.       * activity.setUser(user);
  919.       *
  920.       * return true; }
  921.       */
  922.     public void updateIssueActivityNotification(Integer issueId,
  923.                                                 boolean notificationSent) {

  924.         if (issueId == null) {

  925.             return;

  926.         }

  927.         Collection<IssueActivity> activity = getIssueActivityDAO()
  928.                 .findByIssueId(issueId);

  929.         for (Iterator<IssueActivity> iter = activity.iterator(); iter.hasNext(); ) {

  930.             ((IssueActivity) iter.next()).setNotificationSent(notificationSent);

  931.         }

  932.     }

  933.     /**
  934.      * Adds an attachment to an issue
  935.      *
  936.      * @param attachment The attachment data
  937.      * @param data       The byte data
  938.      */
  939.     public boolean addIssueAttachment(IssueAttachment attachment, byte[] data) {
  940.         Issue issue = attachment.getIssue();

  941.         attachment.setFileName("attachment_issue_" + issue.getId() + "_"
  942.                 + attachment.getOriginalFileName());
  943.         attachment.setFileData((data == null ? new byte[0] : data));

  944.         // TODO: translate activity for adding attachments
  945.         // IssueActivity activityAdd = new IssueActivity(issue,
  946.         //         attachment.getUser(), IssueActivityType.ATTACHMENT_ADDED);
  947.         // activityAdd.setDescription(attachment.getOriginalFileName());
  948.         // issue.getActivities().add(activityAdd);

  949.         if (logger.isDebugEnabled()) {
  950.             logger.debug("addIssueAttachment: adding attachment " + attachment);
  951.         }
  952.         // add attachment to issue
  953.         issue.getAttachments().add(attachment);
  954.         if (logger.isDebugEnabled()) {
  955.             logger.debug("addIssueAttachment: saving updated issue " + issue);
  956.         }
  957.         this.getIssueDAO().saveOrUpdate(issue);
  958.         return true;
  959.     }

  960.     public boolean setIssueAttachmentData(Integer attachmentId, byte[] data) {

  961.         if (attachmentId != null && data != null) {

  962.             IssueAttachment attachment = getIssueAttachmentDAO()
  963.                     .findByPrimaryKey(attachmentId);

  964.             attachment.setFileData(data);

  965.             return true;

  966.         }

  967.         return false;

  968.     }

  969.     public boolean setIssueAttachmentData(String fileName, byte[] data) {

  970.         if (fileName != null && data != null) {

  971.             IssueAttachment attachment = getIssueAttachmentDAO()
  972.                     .findByFileName(fileName);

  973.             attachment.setFileData(data);

  974.             return true;

  975.         }

  976.         return false;

  977.     }

  978.     /**
  979.      * Removes a attachement (deletes it)
  980.      *
  981.      * @param attachmentId the id of the <code>IssueAttachmentBean</code>
  982.      */
  983.     public boolean removeIssueAttachment(Integer attachmentId) {

  984.         IssueAttachment attachementBean = this.getIssueAttachmentDAO()
  985.                 .findByPrimaryKey(attachmentId);

  986.         getIssueAttachmentDAO().delete(attachementBean);

  987.         return true;
  988.     }

  989.     public Integer removeIssueHistoryEntry(Integer entryId, Integer userId) {

  990.         IssueHistory history = getIssueHistoryDAO().findByPrimaryKey(entryId);

  991.         if (history != null) {

  992.             history.setStatus(IssueUtilities.HISTORY_STATUS_REMOVED);

  993.             IssueActivity activity = new IssueActivity();
  994.             activity
  995.                     .setActivityType(org.itracker.model.IssueActivityType.REMOVE_HISTORY);
  996.             activity.setDescription(ITrackerResources
  997.                     .getString("itracker.web.generic.entry")
  998.                     + " "
  999.                     + entryId
  1000.                     + " "
  1001.                     + ITrackerResources
  1002.                     .getString("itracker.web.generic.removed") + ".");

  1003.             getIssueHistoryDAO().delete(history);

  1004.             return history.getIssue().getId();

  1005.         }

  1006.         return Integer.valueOf(-1);

  1007.     }

  1008.     public Project getIssueProject(Integer issueId) {
  1009.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  1010.         Project project = issue.getProject();

  1011.         return project;
  1012.     }

  1013.     public HashSet<Integer> getIssueComponentIds(Integer issueId) {

  1014.         HashSet<Integer> componentIds = new HashSet<Integer>();
  1015.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);
  1016.         Collection<Component> components = issue.getComponents();

  1017.         for (Iterator<Component> iterator = components.iterator(); iterator
  1018.                 .hasNext(); ) {
  1019.             componentIds.add(((Component) iterator.next()).getId());
  1020.         }

  1021.         return componentIds;

  1022.     }

  1023.     public HashSet<Integer> getIssueVersionIds(Integer issueId) {

  1024.         HashSet<Integer> versionIds = new HashSet<Integer>();

  1025.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  1026.         Collection<Version> versions = issue.getVersions();

  1027.         for (Iterator<Version> iterator = versions.iterator(); iterator
  1028.                 .hasNext(); ) {

  1029.             versionIds.add(((Version) iterator.next()).getId());

  1030.         }

  1031.         return versionIds;

  1032.     }

  1033.     public List<IssueActivity> getIssueActivity(Integer issueId) {

  1034.         int i = 0;

  1035.         Collection<IssueActivity> activity = getIssueActivityDAO()
  1036.                 .findByIssueId(issueId);

  1037.         IssueActivity[] activityArray = new IssueActivity[activity.size()];

  1038.         for (Iterator<IssueActivity> iterator = activity.iterator(); iterator
  1039.                 .hasNext(); i++) {

  1040.             activityArray[i] = ((IssueActivity) iterator.next());

  1041.         }

  1042.         return Arrays.asList(activityArray);

  1043.     }

  1044.     /**
  1045.      * TODO move to {@link NotificationService} ?
  1046.      */
  1047.     public List<IssueActivity> getIssueActivity(Integer issueId,
  1048.                                                 boolean notificationSent) {

  1049.         int i = 0;

  1050.         Collection<IssueActivity> activity = getIssueActivityDAO()
  1051.                 .findByIssueIdAndNotification(issueId, notificationSent);

  1052.         IssueActivity[] activityArray = new IssueActivity[activity.size()];

  1053.         for (Iterator<IssueActivity> iterator = activity.iterator(); iterator
  1054.                 .hasNext(); i++) {

  1055.             activityArray[i] = ((IssueActivity) iterator.next());

  1056.         }

  1057.         return Arrays.asList(activityArray);

  1058.     }

  1059.     public Long getAllIssueAttachmentCount() {
  1060.         return getIssueAttachmentDAO().countAll().longValue();
  1061.     }

  1062.     public List<IssueAttachment> getAllIssueAttachments() {
  1063.         logger.warn("getAllIssueAttachments: use of deprecated API");
  1064.         if (logger.isDebugEnabled()) {
  1065.             logger.debug("getAllIssueAttachments: stacktrace was",
  1066.                     new RuntimeException());
  1067.         }

  1068.         List<IssueAttachment> attachments = getIssueAttachmentDAO().findAll();

  1069.         return attachments;
  1070.     }

  1071.     public IssueAttachment getIssueAttachment(Integer attachmentId) {
  1072.         IssueAttachment attachment = getIssueAttachmentDAO().findByPrimaryKey(
  1073.                 attachmentId);

  1074.         return attachment;

  1075.     }

  1076.     public byte[] getIssueAttachmentData(Integer attachmentId) {

  1077.         byte[] data;

  1078.         IssueAttachment attachment = getIssueAttachmentDAO().findByPrimaryKey(
  1079.                 attachmentId);

  1080.         data = attachment.getFileData();

  1081.         return data;

  1082.     }

  1083.     public int getIssueAttachmentCount(Integer issueId) {

  1084.         int i = 0;

  1085.         Issue issue = getIssueDAO().findByPrimaryKey(issueId);

  1086.         Collection<IssueAttachment> attachments = issue.getAttachments();

  1087.         i = attachments.size();

  1088.         return i;

  1089.     }

  1090.     /**
  1091.      * Returns the latest issue history entry for a particular issue.
  1092.      *
  1093.      * @param issueId the id of the issue to return the history entry for.
  1094.      * @return the latest IssueHistory, or null if no entries could be found
  1095.      */
  1096.     public IssueHistory getLastIssueHistory(Integer issueId) {

  1097.         List<IssueHistory> history = getIssueHistoryDAO()
  1098.                 .findByIssueId(issueId);

  1099.         if (null != history && history.size() > 0) {
  1100.             // sort ascending by id
  1101.             Collections.sort(history, AbstractEntity.ID_COMPARATOR);
  1102.             // return last entry in list
  1103.             return history.get(history.size() - 1);
  1104.         }

  1105.         return null;

  1106.     }

  1107.     public int getOpenIssueCountByProjectId(Integer projectId) {

  1108.         Collection<Issue> issues = getIssueDAO().findByProjectAndLowerStatus(
  1109.                 projectId, IssueUtilities.STATUS_RESOLVED);

  1110.         return issues.size();

  1111.     }

  1112.     public int getResolvedIssueCountByProjectId(Integer projectId) {

  1113.         Collection<Issue> issues = getIssueDAO().findByProjectAndHigherStatus(
  1114.                 projectId, IssueUtilities.STATUS_RESOLVED);

  1115.         return issues.size();

  1116.     }

  1117.     public int getTotalIssueCountByProjectId(Integer projectId) {

  1118.         Collection<Issue> issues = getIssueDAO().findByProject(projectId);

  1119.         return issues.size();

  1120.     }

  1121.     public Date getLatestIssueDateByProjectId(Integer projectId) {

  1122.         return getIssueDAO().latestModificationDate(projectId);

  1123.     }

  1124.     public List<Issue> getNextIssues(Integer issueId) {
  1125.         return getIssueDAO().findNextIssues(issueId);
  1126.     }
  1127.     public List<Issue> getPreviousIssues(Integer issueId) {
  1128.         return getIssueDAO().findPreviousIssues(issueId);
  1129.     }

  1130.     public boolean canViewIssue(Integer issueId, User user) {

  1131.         Issue issue = getIssue(issueId);

  1132.         Map<Integer, Set<PermissionType>> permissions = getUserDAO()
  1133.                 .getUsersMapOfProjectsAndPermissionTypes(user);

  1134.         return IssueUtilities.canViewIssue(issue, user.getId(), permissions);

  1135.     }

  1136.     public boolean canViewIssue(Issue issue, User user) {

  1137.         Map<Integer, Set<PermissionType>> permissions = getUserDAO()
  1138.                 .getUsersMapOfProjectsAndPermissionTypes(user);

  1139.         return IssueUtilities.canViewIssue(issue, user.getId(), permissions);

  1140.     }

  1141.     private UserDAO getUserDAO() {
  1142.         return userDAO;
  1143.     }

  1144.     private IssueDAO getIssueDAO() {
  1145.         return issueDAO;
  1146.     }

  1147.     private ProjectDAO getProjectDAO() {
  1148.         return projectDAO;
  1149.     }

  1150.     private IssueActivityDAO getIssueActivityDAO() {
  1151.         return issueActivityDAO;
  1152.     }

  1153.     private VersionDAO getVersionDAO() {
  1154.         return this.versionDAO;
  1155.     }

  1156.     private ComponentDAO getComponentDAO() {
  1157.         return this.componentDAO;
  1158.     }

  1159.     private CustomFieldDAO getCustomFieldDAO() {
  1160.         return customFieldDAO;
  1161.     }

  1162.     private IssueHistoryDAO getIssueHistoryDAO() {
  1163.         return issueHistoryDAO;
  1164.     }

  1165.     private IssueRelationDAO getIssueRelationDAO() {
  1166.         return issueRelationDAO;
  1167.     }

  1168.     private IssueAttachmentDAO getIssueAttachmentDAO() {
  1169.         return issueAttachmentDAO;
  1170.     }

  1171.     /**
  1172.      * get total size of all attachments in database
  1173.      */
  1174.     public Long getAllIssueAttachmentSize() {

  1175.         return getIssueAttachmentDAO().totalAttachmentsSize().longValue() / 1024;

  1176.     }

  1177.     public List<Issue> searchIssues(IssueSearchQuery queryModel, User user,
  1178.                                     Map<Integer, Set<PermissionType>> userPermissions)
  1179.             throws IssueSearchException {
  1180.         return getIssueDAO().query(queryModel, user, userPermissions);
  1181.     }

  1182.     public Long totalSystemIssuesAttachmentSize() {
  1183.         return getIssueAttachmentDAO().totalAttachmentsSize();
  1184.     }

  1185.     public ConfigurationService getConfigurationService() {
  1186.         return configurationService;
  1187.     }

  1188.     public void setUserDAO(UserDAO userDAO) {
  1189.         this.userDAO = userDAO;
  1190.     }

  1191.     public void setConfigurationService(ConfigurationService configurationService) {
  1192.         this.configurationService = configurationService;
  1193.     }

  1194.     public void setCustomFieldDAO(CustomFieldDAO customFieldDAO) {
  1195.         this.customFieldDAO = customFieldDAO;
  1196.     }

  1197.     public void setProjectDAO(ProjectDAO projectDAO) {
  1198.         this.projectDAO = projectDAO;
  1199.     }

  1200.     public void setIssueDAO(IssueDAO issueDAO) {
  1201.         this.issueDAO = issueDAO;
  1202.     }

  1203.     public void setIssueHistoryDAO(IssueHistoryDAO issueHistoryDAO) {
  1204.         this.issueHistoryDAO = issueHistoryDAO;
  1205.     }

  1206.     public void setIssueRelationDAO(IssueRelationDAO issueRelationDAO) {
  1207.         this.issueRelationDAO = issueRelationDAO;
  1208.     }

  1209.     public void setIssueAttachmentDAO(IssueAttachmentDAO issueAttachmentDAO) {
  1210.         this.issueAttachmentDAO = issueAttachmentDAO;
  1211.     }

  1212.     public void setComponentDAO(ComponentDAO componentDAO) {
  1213.         this.componentDAO = componentDAO;
  1214.     }

  1215.     public void setIssueActivityDAO(IssueActivityDAO issueActivityDAO) {
  1216.         this.issueActivityDAO = issueActivityDAO;
  1217.     }

  1218.     public void setVersionDAO(VersionDAO versionDAO) {
  1219.         this.versionDAO = versionDAO;
  1220.     }

  1221.     public void setNotificationService(NotificationService notificationService) {
  1222.         this.notificationService = notificationService;
  1223.     }
  1224. }