View Javadoc

1   /*
2   
3    * This software was designed and created by Jason Carroll.
4   
5    * Copyright (c) 2002, 2003, 2004 Jason Carroll.
6   
7    * The author can be reached at jcarroll@cowsultants.com
8   
9    * ITracker website: http://www.cowsultants.com
10  
11   * ITracker forums: http://www.cowsultants.com/phpBB/index.php
12  
13   *
14  
15   * This program is free software; you can redistribute it and/or modify
16  
17   * it only under the terms of the GNU General Public License as published by
18  
19   * the Free Software Foundation; either version 2 of the License, or
20  
21   * (at your option) any later version.
22  
23   *
24  
25   * This program is distributed in the hope that it will be useful,
26  
27   * but WITHOUT ANY WARRANTY; without even the implied warranty of
28  
29   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30  
31   * GNU General Public License for more details.
32  
33   */
34  
35  package org.itracker.services.implementations;
36  
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.Date;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  import org.apache.log4j.Logger;
49  import org.itracker.core.resources.ITrackerResources;
50  import org.itracker.model.AbstractEntity;
51  import org.itracker.model.Component;
52  import org.itracker.model.CustomField;
53  import org.itracker.model.Issue;
54  import org.itracker.model.IssueActivity;
55  import org.itracker.model.IssueActivityType;
56  import org.itracker.model.IssueAttachment;
57  import org.itracker.model.IssueField;
58  import org.itracker.model.IssueHistory;
59  import org.itracker.model.IssueRelation;
60  import org.itracker.model.IssueSearchQuery;
61  import org.itracker.model.Notification;
62  import org.itracker.model.PermissionType;
63  import org.itracker.model.Project;
64  import org.itracker.model.Status;
65  import org.itracker.model.User;
66  import org.itracker.model.Version;
67  import org.itracker.model.Notification.Role;
68  import org.itracker.model.Notification.Type;
69  import org.itracker.persistence.dao.ComponentDAO;
70  import org.itracker.persistence.dao.CustomFieldDAO;
71  import org.itracker.persistence.dao.IssueActivityDAO;
72  import org.itracker.persistence.dao.IssueAttachmentDAO;
73  import org.itracker.persistence.dao.IssueDAO;
74  import org.itracker.persistence.dao.IssueHistoryDAO;
75  import org.itracker.persistence.dao.IssueRelationDAO;
76  import org.itracker.persistence.dao.ProjectDAO;
77  import org.itracker.persistence.dao.UserDAO;
78  import org.itracker.persistence.dao.VersionDAO;
79  import org.itracker.services.IssueService;
80  import org.itracker.services.NotificationService;
81  import org.itracker.services.exceptions.IssueSearchException;
82  import org.itracker.services.exceptions.ProjectException;
83  import org.itracker.services.util.IssueUtilities;
84  import org.itracker.web.util.ServletContextUtils;
85  
86  /**
87   * Issue related service layer. A bit "fat" at this time, because of being a
88   * direct EJB porting. Going go get thinner over time
89   * 
90   * @author ricardo
91   * 
92   */
93  
94  //TODO: Cleanup this file, go through all issues, todos, etc.
95  
96  public class IssueServiceImpl implements IssueService {
97  
98  	private static final Logger logger = Logger
99  	.getLogger(IssueServiceImpl.class);
100 
101 	private CustomFieldDAO customFieldDAO;
102 
103 	private UserDAO userDAO;
104 
105 	private ProjectDAO projectDAO;
106 
107 	private IssueDAO issueDAO;
108 
109 	private IssueHistoryDAO issueHistoryDAO;
110 
111 	private IssueRelationDAO issueRelationDAO;
112 
113 	private IssueAttachmentDAO issueAttachmentDAO;
114 
115 	private ComponentDAO componentDAO;
116 
117 	private IssueActivityDAO issueActivityDAO;
118 
119 	private VersionDAO versionDAO;
120 
121 	private NotificationService notificationService;
122 
123 	public IssueServiceImpl(UserDAO userDAO, ProjectDAO projectDAO,
124 			IssueDAO issueDAO, IssueHistoryDAO issueHistoryDAO,
125 			IssueRelationDAO issueRelationDAO,
126 			IssueAttachmentDAO issueAttachmentDAO, ComponentDAO componentDAO,
127 			IssueActivityDAO issueActivityDAO, VersionDAO versionDAO,
128 			CustomFieldDAO customFieldDAO,
129 			NotificationService notificationService) {
130 
131 		this.userDAO = userDAO;
132 		this.projectDAO = projectDAO;
133 		this.issueDAO = issueDAO;
134 		this.issueHistoryDAO = issueHistoryDAO;
135 		this.issueRelationDAO = issueRelationDAO;
136 		this.issueAttachmentDAO = issueAttachmentDAO;
137 		this.componentDAO = componentDAO;
138 		this.issueActivityDAO = issueActivityDAO;
139 		this.versionDAO = versionDAO;
140 		this.customFieldDAO = customFieldDAO;
141 		this.notificationService = notificationService;
142 	}
143 
144 	public Issue getIssue(Integer issueId) {
145 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
146 		return issue;
147 	}
148 
149 	/**
150 	 * @deprecated don't use to expensive memory use!
151 	 */
152 	public List<Issue> getAllIssues() {
153 		logger.warn("getAllIssues: use of deprecated API");
154 		if (logger.isDebugEnabled()) {
155 			logger
156 			.debug("getAllIssues: stacktrace was",
157 					new RuntimeException());
158 		}
159 		return getIssueDAO().findAll();
160 	}
161 
162 	/**
163 	 * Added implementation to make proper count of ALL issues, instead select
164 	 * them in a list and return its size
165 	 */
166 	public Long getNumberIssues() {
167 		return getIssueDAO().countAllIssues();
168 	}
169 
170 	public List<Issue> getIssuesCreatedByUser(Integer userId) {
171 		return getIssuesCreatedByUser(userId, true);
172 	}
173 
174 	public List<Issue> getIssuesCreatedByUser(Integer userId,
175 			boolean availableProjectsOnly) {
176 		final List<Issue> issues;
177 
178 		if (availableProjectsOnly) {
179 			issues = getIssueDAO().findByCreatorInAvailableProjects(userId,
180 					IssueUtilities.STATUS_CLOSED);
181 		} else {
182 			issues = getIssueDAO().findByCreator(userId,
183 					IssueUtilities.STATUS_CLOSED);
184 		}
185 		return issues;
186 	}
187 
188 	public List<Issue> getIssuesOwnedByUser(Integer userId) {
189 
190 		return getIssuesOwnedByUser(userId, true);
191 
192 	}
193 
194 	public List<Issue> getIssuesOwnedByUser(Integer userId,
195 			boolean availableProjectsOnly) {
196 		final List<Issue> issues;
197 
198 		if (availableProjectsOnly) {
199 			issues = getIssueDAO().findByOwnerInAvailableProjects(userId,
200 					IssueUtilities.STATUS_RESOLVED);
201 		} else {
202 			issues = getIssueDAO().findByOwner(userId,
203 					IssueUtilities.STATUS_RESOLVED);
204 		}
205 		return issues;
206 	}
207 
208 	public List<Issue> getIssuesWatchedByUser(Integer userId) {
209 		return getIssuesWatchedByUser(userId, true);
210 	}
211 
212 	/**
213 	 * TODO move to {@link NotificationService}
214 	 */
215 	public List<Issue> getIssuesWatchedByUser(Integer userId,
216 			boolean availableProjectsOnly) {
217 		final List<Issue> issues;
218 
219 		if (availableProjectsOnly) {
220 			issues = getIssueDAO().findByNotificationInAvailableProjects(
221 					userId, IssueUtilities.STATUS_CLOSED);
222 		} else {
223 			issues = getIssueDAO().findByNotification(userId,
224 					IssueUtilities.STATUS_CLOSED);
225 		}
226 		return issues;
227 	}
228 
229 	public List<Issue> getUnassignedIssues() {
230 		return getUnassignedIssues(true);
231 	}
232 
233 	public List<Issue> getUnassignedIssues(boolean availableProjectsOnly) {
234 		final List<Issue> issues;
235 
236 		if (availableProjectsOnly) {
237 			issues = getIssueDAO()
238 			.findByStatusLessThanEqualToInAvailableProjects(
239 					IssueUtilities.STATUS_UNASSIGNED);
240 		} else {
241 			issues = getIssueDAO().findByStatusLessThanEqualTo(
242 					IssueUtilities.STATUS_UNASSIGNED);
243 		}
244 		return issues;
245 	}
246 
247 	/**
248 	 * 
249 	 * Returns all issues with a status equal to the given status number
250 	 * 
251 	 * 
252 	 * 
253 	 * @param status
254 	 * 
255 	 *            the status to compare
256 	 * 
257 	 * @return an array of IssueModels that match the criteria
258 	 * 
259 	 */
260 
261 	public List<Issue> getIssuesWithStatus(int status) {
262 		List<Issue> issues = getIssueDAO().findByStatus(status);
263 		return issues;
264 	}
265 
266 	/**
267 	 * 
268 	 * Returns all issues with a status less than the given status number
269 	 * 
270 	 * 
271 	 * 
272 	 * @param status
273 	 * 
274 	 *            the status to compare
275 	 * 
276 	 * @return an array of IssueModels that match the criteria
277 	 */
278 
279 	public List<Issue> getIssuesWithStatusLessThan(int status) {
280 		List<Issue> issues = getIssueDAO().findByStatusLessThan(status);
281 		return issues;
282 	}
283 
284 	/**
285 	 * 
286 	 * Returns all issues with a severity equal to the given severity number
287 	 * 
288 	 * 
289 	 * 
290 	 * @param severity
291 	 * 
292 	 *            the severity to compare
293 	 * 
294 	 * @return an array of IssueModels that match the criteria
295 	 * 
296 	 */
297 
298 	public List<Issue> getIssuesWithSeverity(int severity) {
299 		List<Issue> issues = getIssueDAO().findBySeverity(severity);
300 		return issues;
301 
302 	}
303 
304 	public List<Issue> getIssuesByProjectId(Integer projectId) {
305 		return getIssuesByProjectId(projectId, IssueUtilities.STATUS_END);
306 	}
307 
308 	public List<Issue> getIssuesByProjectId(Integer projectId, int status) {
309 		List<Issue> issues = getIssueDAO().findByProjectAndLowerStatus(
310 				projectId, status);
311 		return issues;
312 	}
313 
314 	public User getIssueCreator(Integer issueId) {
315 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
316 		User user = issue.getCreator();
317 		return user;
318 
319 	}
320 
321 	public User getIssueOwner(Integer issueId) {
322 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
323 		User user = issue.getOwner();
324 
325 		return user;
326 
327 	}
328 
329 	public List<Component> getIssueComponents(Integer issueId) {
330 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
331 		List<Component> components = issue.getComponents();
332 
333 		return components;
334 	}
335 
336 	public List<Version> getIssueVersions(Integer issueId) {
337 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
338 
339 		List<Version> versions = issue.getVersions();
340 		return versions;
341 	}
342 
343 	public List<IssueAttachment> getIssueAttachments(Integer issueId) {
344 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
345 
346 		List<IssueAttachment> attachments = issue.getAttachments();
347 		return attachments;
348 	}
349 
350 	/**
351 	 * Old implementation is left here, commented, because it checked for
352 	 * history entry status. This feature was not finished, I think (RJST)
353 	 */
354 	public List<IssueHistory> getIssueHistory(Integer issueId) {
355 		return getIssueDAO().findByPrimaryKey(issueId).getHistory();
356 	}
357 
358 	public Issue createIssue(Issue issue, Integer projectId, Integer userId,
359 			Integer createdById) throws ProjectException {
360 		Project project = getProjectDAO().findByPrimaryKey(projectId);
361 		User creator = getUserDAO().findByPrimaryKey(userId);
362 
363 		if (project.getStatus() != Status.ACTIVE) {
364 			throw new ProjectException("Project is not active.");
365 		}
366 
367 		IssueActivity activity = new IssueActivity(issue, creator,
368 				IssueActivityType.ISSUE_CREATED);
369 		activity.setDescription(ITrackerResources
370 				.getString("itracker.activity.system.createdfor")
371 				+ " " + creator.getFirstName() + " " + creator.getLastName());
372 
373 		activity.setIssue(issue);
374 
375 		if (!(createdById == null || createdById.equals(userId))) {
376 
377 			User createdBy = getUserDAO().findByPrimaryKey(createdById);
378 			activity.setUser(createdBy);
379 
380 			Notification watchModel = new Notification();
381 
382 			watchModel.setUser(createdBy);
383 
384 			watchModel.setIssue(issue);
385 
386 			watchModel.setRole(Notification.Role.CONTRIBUTER);
387 
388 			issue.getNotifications().add(watchModel);
389 			
390 //			Does save issue after saving notificaton ->  not-null property references a null or transient value: org.itracker.model.Notification.issue
391 //			Will be saved by cascade when saving issue
392 //			notificationService.addIssueNotification(watchModel);
393 
394 		}
395 
396 		List<IssueActivity> activities = new ArrayList<IssueActivity>();
397 		activities.add(activity);
398 		issue.setActivities(activities);
399 
400 		issue.setProject(project);
401 
402 		issue.setCreator(creator);
403 
404 		// save
405 		getIssueDAO().save(issue);
406 
407 		return issue;
408 	}
409 
410 	/**
411 	 * Save a modified issue to the persistence layer
412 	 * 
413 	 * @param issueDirty
414 	 *            the changed, unsaved issue to update on persistency layer
415 	 * @param userId
416 	 *            the user-id of the changer
417 	 * 
418 	 */
419 	public Issue updateIssue(final Issue issueDirty, final Integer userId)
420 	throws ProjectException {
421 
422 		String existingTargetVersion = null;
423 
424 		// detach the modified Issue form the Hibernate Session
425 		getIssueDAO().detach(issueDirty);
426 		// Retrieve the Issue from Hibernate Session and refresh it from
427 		// Hibernate Session to previous state.
428 		Issue persistedIssue = getIssueDAO().findByPrimaryKey(
429 				issueDirty.getId());
430 
431 		getIssueDAO().refresh(persistedIssue);
432 		if (logger.isDebugEnabled()) {
433 			logger.debug("updateIssue: updating issue " + issueDirty
434 					+ "\n(from " + persistedIssue + ")");
435 		}
436 
437 		User user = getUserDAO().findByPrimaryKey(userId);
438 
439 		if (persistedIssue.getProject().getStatus() != Status.ACTIVE) {
440 			throw new ProjectException("Project "
441 					+ persistedIssue.getProject().getName() + " is not active.");
442 		}
443 
444 		if (!persistedIssue.getDescription().equalsIgnoreCase(
445 				issueDirty.getDescription())) {
446 
447 			if (logger.isDebugEnabled()) {
448 				logger.debug("updateIssue: updating description from "
449 						+ persistedIssue.getDescription());
450 			}
451 			IssueActivity activity = new IssueActivity();
452 			activity.setActivityType(IssueActivityType.DESCRIPTION_CHANGE);
453 			activity.setDescription(ITrackerResources
454 					.getString("itracker.web.generic.from")
455 					+ ": " + persistedIssue.getDescription());
456 			activity.setUser(user);
457 			activity.setIssue(issueDirty);
458 			issueDirty.getActivities().add(activity);
459 
460 		}
461 
462 		if (persistedIssue.getResolution() != null
463 				&& !persistedIssue.getResolution().equalsIgnoreCase(
464 						issueDirty.getResolution())) {
465 
466 			IssueActivity activity = new IssueActivity();
467 			activity.setActivityType(IssueActivityType.RESOLUTION_CHANGE);
468 			activity.setDescription(ITrackerResources
469 					.getString("itracker.web.generic.from")
470 					+ ": " + persistedIssue.getResolution());
471 			activity.setUser(user);
472 			activity.setIssue(issueDirty);
473 			issueDirty.getActivities().add(activity);
474 		}
475 
476 		if (null == persistedIssue.getStatus()
477 				|| !persistedIssue.getStatus().equals(issueDirty.getStatus())) {
478 			IssueActivity activity = new IssueActivity();
479 			activity.setActivityType(IssueActivityType.STATUS_CHANGE);
480 			activity.setDescription(IssueUtilities.getStatusName(persistedIssue
481 					.getStatus())
482 					+ " "
483 					+ ITrackerResources.getString("itracker.web.generic.to")
484 					+ " "
485 					+ IssueUtilities.getStatusName(issueDirty.getStatus()));
486 			activity.setUser(user);
487 			activity.setIssue(issueDirty);
488 			issueDirty.getActivities().add(activity);
489 		}
490 
491 		if (issueDirty.getSeverity() != null
492 				&& !issueDirty.getSeverity().equals(
493 						persistedIssue.getSeverity())
494 						&& issueDirty.getSeverity() != -1) {
495 
496 			IssueActivity activity = new IssueActivity();
497 			activity.setActivityType(IssueActivityType.SEVERITY_CHANGE);
498 			// FIXME why does it state Critical to Critical when it should Major to Critical!?
499 			activity.setDescription(IssueUtilities
500 					.getSeverityName(persistedIssue.getSeverity())
501 					+ " "
502 					+ ITrackerResources.getString("itracker.web.generic.to")
503 					+ " "
504 					+ IssueUtilities.getSeverityName(issueDirty.getSeverity()));
505 
506 			activity.setUser(user);
507 			activity.setIssue(issueDirty);
508 			issueDirty.getActivities().add(activity);
509 		}
510 
511 		if (persistedIssue.getTargetVersion() != null
512 				&& issueDirty.getTargetVersion() != null
513 				&& !persistedIssue.getTargetVersion().getId().equals(
514 						issueDirty.getTargetVersion().getId())) {
515 			existingTargetVersion = persistedIssue.getTargetVersion()
516 			.getNumber();
517 			Version version = this.getVersionDAO().findByPrimaryKey(
518 					issueDirty.getTargetVersion().getId());
519 
520 			IssueActivity activity = new IssueActivity();
521 			activity.setActivityType(IssueActivityType.TARGETVERSION_CHANGE);
522 			String description = existingTargetVersion + " "
523 			+ ITrackerResources.getString("itracker.web.generic.to")
524 			+ " ";
525 			description += version.getNumber();
526 			activity.setDescription(description);
527 			activity.setUser(user);
528 			activity.setIssue(issueDirty);
529 			issueDirty.getActivities().add(activity);
530 		}
531 
532 		// (re-)assign issue
533 		User newOwner = issueDirty.getOwner();
534 		issueDirty.setOwner(persistedIssue.getOwner());
535 		if (logger.isDebugEnabled()) {
536 			logger.debug("updateIssue: assigning from " + issueDirty.getOwner()
537 					+ " to " + newOwner);
538 		}
539 		assignIssue(issueDirty, newOwner, user, false);
540 		if (logger.isDebugEnabled()) {
541 			logger.debug("updateIssue: updated assignment: " + issueDirty);
542 		}
543 
544 		if (logger.isDebugEnabled()) {
545 			logger.debug("updateIssue: merging issue " + issueDirty + " to "
546 					+ persistedIssue);
547 		}
548 
549 		persistedIssue = getIssueDAO().merge(issueDirty);
550 
551 		if (logger.isDebugEnabled()) {
552 			logger.debug("updateIssue: merged issue for saving: "
553 					+ persistedIssue);
554 		}
555 		getIssueDAO().saveOrUpdate(persistedIssue);
556 		if (logger.isDebugEnabled()) {
557 			logger.debug("updateIssue: saved issue: " + persistedIssue);
558 		}
559 		return persistedIssue;
560 	}
561 
562 	/**
563 	 * 
564 	 * Moves an issues from its current project to a new project.
565 	 * 
566 	 * 
567 	 * 
568 	 * @param issue
569 	 * 
570 	 *            an Issue of the issue to move
571 	 * 
572 	 * @param projectId
573 	 * 
574 	 *            the id of the target project
575 	 * 
576 	 * @param userId
577 	 * 
578 	 *            the id of the user that is moving the issue
579 	 * 
580 	 * @return an Issue of the issue after it has been moved
581 	 */
582 
583 	public Issue moveIssue(Issue issue, Integer projectId, Integer userId) {
584 
585 		if (logger.isDebugEnabled()) {
586 			logger.debug("moveIssue: " + issue + " to project#" + projectId
587 					+ ", user#" + userId);
588 		}
589 
590 		Project project = getProjectDAO().findByPrimaryKey(projectId);
591 		User user = getUserDAO().findByPrimaryKey(userId);
592 
593 		if (logger.isDebugEnabled()) {
594 			logger.debug("moveIssue: " + issue + " to project: " + project
595 					+ ", user: " + user);
596 		}
597 
598 		IssueActivity activity = new IssueActivity();
599 		activity
600 		.setActivityType(org.itracker.model.IssueActivityType.ISSUE_MOVE);
601 		activity.setDescription(issue.getProject().getName() + " "
602 				+ ITrackerResources.getString("itracker.web.generic.to") + " "
603 				+ project.getName());
604 		activity.setUser(user);
605 		activity.setIssue(issue);
606 		issue.setProject(project);
607 
608 		issue.getActivities().add(activity);
609 
610 		if (logger.isDebugEnabled()) {
611 			logger.debug("moveIssue: updated issue: " + issue);
612 		}
613 		try {
614 			getIssueDAO().saveOrUpdate(issue);
615 		} catch (Exception e) {
616 			logger.error("moveIssue: failed to save issue: " + issue, e);
617 			return null;
618 		}
619 		if (logger.isDebugEnabled()) {
620 			logger.debug("moveIssue: saved move-issue to " + project);
621 		}
622 		return issue;
623 
624 	}
625 
626 	/**
627 	 * this should not exist. adding an history entry should be adding the
628 	 * history entry to the domain object and saving the object...
629 	 */
630 	public boolean addIssueHistory(IssueHistory history) {
631 		getIssueHistoryDAO().saveOrUpdate(history);
632 		history.getIssue().getHistory().add(history);
633 		getIssueDAO().saveOrUpdate(history.getIssue());
634 		return true;
635 	}
636 
637 	/**
638 	 * TODO maybe it has no use at all. is it obsolete? when I'd set the
639 	 * issue-fields on an issue and then save/update issue, would it be good
640 	 * enough?
641 	 */
642 	public boolean setIssueFields(Integer issueId, List<IssueField> fields) {
643 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
644 
645 		setIssueFields(issue, fields, true);
646 
647 		return true;
648 	}
649 
650 	private boolean setIssueFields(Issue issue, List<IssueField> fields,
651 			boolean save) {
652 
653 		List<IssueField> issueFields = issue.getFields();
654 
655 		if (fields.size() > 0) {
656 			for (int i = 0; i < fields.size(); i++) {
657 
658 				IssueField field = fields.get(i);
659 				if (issueFields.contains(field)) {
660 					issueFields.remove(field);
661 				}
662 
663 				CustomField customField = getCustomFieldDAO().findByPrimaryKey(
664 						fields.get(i).getCustomField().getId());
665 				field.setCustomField(customField);
666 				field.setIssue(issue);
667 
668 				// what date value?
669 				// field.setDateValue(new Timestamp(new Date().getTime()));
670 				issueFields.add(field);
671 			}
672 		}
673 		issue.setFields(issueFields);
674 
675 		if (save) {
676 			logger.debug("setIssueFields: save was true");
677 			getIssueDAO().saveOrUpdate(issue);
678 		}
679 		return true;
680 	}
681 
682 	public boolean setIssueComponents(Integer issueId,
683 			HashSet<Integer> componentIds, Integer userId) {
684 
685 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
686 		List<Component> components = new ArrayList<Component>(componentIds
687 				.size());
688 		User user = userDAO.findByPrimaryKey(userId);
689 		Iterator<Integer> idIt = componentIds.iterator();
690 		while (idIt.hasNext()) {
691 			Integer id = (Integer) idIt.next();
692 			Component c = getComponentDAO().findById(id);
693 			components.add(c);
694 		}
695 
696 		setIssueComponents(issue, components, user, true);
697 		return true;
698 	}
699 
700 	private boolean setIssueComponents(Issue issue, List<Component> components,
701 			User user, boolean save) {
702 
703 		if (issue.getComponents() == null) {
704 			if (logger.isInfoEnabled()) {
705 				logger.info("setIssueComponents: components was null");
706 			}
707 			issue.setComponents(new ArrayList<Component>(components.size()));
708 		}
709 		if (components.isEmpty() && !issue.getComponents().isEmpty()) {
710 			addComponentsModifiedActivity(issue, user, new StringBuilder(
711 					ITrackerResources.getString("itracker.web.generic.all"))
712 			.append(" ").append(
713 					ITrackerResources
714 					.getString("itracker.web.generic.removed"))
715 					.toString());
716 			issue.getComponents().clear();
717 		} else {
718 			Collections.sort(issue.getComponents(), Component.NAME_COMPARATOR);
719 
720 			for (Iterator<Component> iterator = issue.getComponents()
721 					.iterator(); iterator.hasNext();) {
722 				Component component = (Component) iterator.next();
723 				if (components.contains(component)) {
724 					components.remove(component);
725 				} else {
726 					addComponentsModifiedActivity(issue, user,
727 							new StringBuilder(ITrackerResources
728 									.getString("itracker.web.generic.removed"))
729 					.append(": ").append(component.getName())
730 					.toString());
731 					iterator.remove();
732 				}
733 			}
734 			Collections.sort(components, Component.NAME_COMPARATOR);
735 			for (Iterator<Component> iterator = components.iterator(); iterator
736 			.hasNext();) {
737 
738 				Component component = iterator.next();
739 				if (!issue.getComponents().contains(component)) {
740 					addComponentsModifiedActivity(issue, user,
741 							new StringBuilder(ITrackerResources
742 									.getString("itracker.web.generic.added"))
743 					.append(": ").append(component.getName())
744 					.toString());
745 					issue.getComponents().add(component);
746 				}
747 			}
748 		}
749 
750 		if (save) {
751 			if (logger.isDebugEnabled()) {
752 				logger.debug("setIssueComponents: save was true");
753 			}
754 			getIssueDAO().saveOrUpdate(issue);
755 		}
756 		return true;
757 
758 	}
759 
760 	/**
761 	 * used by setIssueComponents for adding change activities
762 	 * 
763 	 * @param issue
764 	 * @param user
765 	 * @param description
766 	 */
767 	private void addComponentsModifiedActivity(Issue issue, User user,
768 			String description) {
769 		IssueActivity activity = new IssueActivity();
770 		activity
771 		.setActivityType(org.itracker.model.IssueActivityType.COMPONENTS_MODIFIED);
772 		activity.setDescription(description);
773 		activity.setIssue(issue);
774 		activity.setUser(user);
775 		issue.getActivities().add(activity);
776 	}
777 
778 	private boolean setIssueVersions(Issue issue, List<Version> versions,
779 			User user, boolean save) {
780 
781 		if (issue.getVersions() == null) {
782 			if (logger.isInfoEnabled()) {
783 				logger.info("setIssueVersions: versions were null!");
784 			}
785 			issue.setVersions(new ArrayList<Version>());
786 		}
787 
788 		if (versions.isEmpty() && !issue.getVersions().isEmpty()) {
789 
790 			addVersionsModifiedActivity(issue, user, new StringBuilder(
791 					ITrackerResources.getString("itracker.web.generic.all"))
792 			.append(" ").append(
793 					ITrackerResources
794 					.getString("itracker.web.generic.removed"))
795 					.toString());
796 			issue.getVersions().clear();
797 		} else {
798 
799 			Collections.sort(issue.getVersions(), Version.VERSION_COMPARATOR);
800 
801 			StringBuilder changesBuf = new StringBuilder();
802 			for (Iterator<Version> iterator = issue.getVersions().iterator(); iterator
803 			.hasNext();) {
804 
805 				Version version = iterator.next();
806 				if (versions.contains(version)) {
807 					versions.remove(version);
808 				} else {
809 					if (changesBuf.length() > 0) {
810 						changesBuf.append(", ");
811 					}
812 					changesBuf.append(version.getNumber());
813 					iterator.remove();
814 				}
815 			}
816 
817 			if (changesBuf.length() > 0) {
818 				addVersionsModifiedActivity(issue, user, new StringBuilder(
819 						ITrackerResources
820 						.getString("itracker.web.generic.removed"))
821 				.append(": ").append(changesBuf).toString());
822 			}
823 
824 			changesBuf = new StringBuilder();
825 
826 			Collections.sort(versions, Version.VERSION_COMPARATOR);
827 			for (Iterator<Version> iterator = versions.iterator(); iterator
828 			.hasNext();) {
829 
830 				Version version = iterator.next();
831 				if (changesBuf.length() > 0) {
832 					changesBuf.append(", ");
833 				}
834 				changesBuf.append(version.getNumber());
835 				issue.getVersions().add(version);
836 			}
837 			if (changesBuf.length() > 0) {
838 				addVersionsModifiedActivity(issue, user, new StringBuilder(
839 						ITrackerResources
840 						.getString("itracker.web.generic.added"))
841 				.append(": ").append(changesBuf).toString());
842 			}
843 		}
844 		if (save) {
845 			if (logger.isDebugEnabled()) {
846 				logger.debug("setIssueVersions: updating issue: " + issue);
847 			}
848 			getIssueDAO().saveOrUpdate(issue);
849 		}
850 
851 		return true;
852 	}
853 
854 	/**
855 	 * used by setIssueComponents for adding change activities
856 	 * 
857 	 * @param issue
858 	 * @param user
859 	 * @param description
860 	 */
861 	private void addVersionsModifiedActivity(Issue issue, User user,
862 			String description) {
863 		IssueActivity activity = new IssueActivity();
864 		activity
865 		.setActivityType(org.itracker.model.IssueActivityType.TARGETVERSION_CHANGE);
866 		activity.setDescription(description);
867 		activity.setIssue(issue);
868 		activity.setUser(user);
869 		issue.getActivities().add(activity);
870 	}
871 
872 	public boolean setIssueVersions(Integer issueId,
873 			HashSet<Integer> versionIds, Integer userId) {
874 
875 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
876 		User user = userDAO.findByPrimaryKey(userId);
877 		// load versions from ids
878 		ArrayList<Version> versions = new ArrayList<Version>(versionIds.size());
879 		Iterator<Integer> versionsIdIt = versionIds.iterator();
880 		while (versionsIdIt.hasNext()) {
881 			Integer id = versionsIdIt.next();
882 			versions.add(getVersionDAO().findByPrimaryKey(id));
883 		}
884 
885 		return setIssueVersions(issue, versions, user, true);
886 	}
887 
888 	public IssueRelation getIssueRelation(Integer relationId) {
889 
890 		IssueRelation issueRelation = getIssueRelationDAO().findByPrimaryKey(
891 				relationId);
892 
893 		return issueRelation;
894 
895 	}
896 
897 	/**
898 	 * add a relation between two issues.
899 	 * 
900 	 * TODO: There is no relation saved to database yet?
901 	 */
902 	public boolean addIssueRelation(Integer issueId, Integer relatedIssueId,
903 			int relationType, Integer userId) {
904 
905 		User user = getUserDAO().findByPrimaryKey(userId);
906 
907 		if (null == user) {
908 			throw new IllegalArgumentException("Invalid user-id: " + userId);
909 		}
910 
911 		if (issueId != null && relatedIssueId != null) {
912 
913 			int matchingRelationType = IssueUtilities
914 			.getMatchingRelationType(relationType);
915 
916 			// if(matchingRelationType < 0) {
917 
918 			// throw new CreateException("Unable to find matching relation type
919 
920 			// for type: " + relationType);
921 
922 			// }
923 
924 			Issue issue = getIssueDAO().findByPrimaryKey(issueId);
925 
926 			Issue relatedIssue = getIssueDAO().findByPrimaryKey(relatedIssueId);
927 
928 			IssueRelation relationA = new IssueRelation();
929 
930 			relationA.setRelationType(relationType);
931 
932 			// relationA.setMatchingRelationId(relationBId);
933 
934 			relationA.setIssue(issue);
935 
936 			relationA.setRelatedIssue(relatedIssue);
937 
938 			// set to 0 first, later reassign to relationB.id
939 			relationA.setMatchingRelationId(0);
940 
941 			relationA.setLastModifiedDate(new java.sql.Timestamp(new Date()
942 			.getTime()));
943 
944 			getIssueRelationDAO().saveOrUpdate(relationA);
945 
946 			IssueRelation relationB = new IssueRelation();
947 
948 			relationB.setRelationType(matchingRelationType);
949 
950 			// relationB.setMatchingRelationId(relationAId);
951 
952 			relationB.setIssue(relatedIssue);
953 
954 			relationB.setRelatedIssue(issue);
955 
956 			relationB.setMatchingRelationId(relationA.getId());
957 
958 			relationB.setLastModifiedDate(new java.sql.Timestamp(new Date()
959 			.getTime()));
960 
961 			getIssueRelationDAO().saveOrUpdate(relationB);
962 
963 			relationA.setMatchingRelationId(relationB.getId());
964 			getIssueRelationDAO().saveOrUpdate(relationA);
965 
966 			IssueActivity activity = new IssueActivity();
967 			activity
968 			.setActivityType(org.itracker.model.IssueActivityType.RELATION_ADDED);
969 			activity.setDescription(ITrackerResources.getString(
970 					"itracker.activity.relation.add", new Object[] {
971 							IssueUtilities.getRelationName(relationType),
972 							relatedIssueId }));
973 
974 			activity.setIssue(issue);
975 			issue.getActivities().add(activity);
976 			// need to set user here
977 			activity.setUser(user);
978 			// need to save here
979 			getIssueDAO().saveOrUpdate(issue);
980 
981 			activity = new IssueActivity();
982 			activity
983 			.setActivityType(org.itracker.model.IssueActivityType.RELATION_ADDED);
984 			activity.setDescription(ITrackerResources.getString(
985 					"itracker.activity.relation.add", new Object[] {
986 							IssueUtilities
987 							.getRelationName(matchingRelationType),
988 							issueId }));
989 			activity.setIssue(relatedIssue);
990 			activity.setUser(user);
991 			relatedIssue.getActivities().add(activity);
992 			getIssueDAO().saveOrUpdate(relatedIssue);
993 			return true;
994 
995 		}
996 
997 		return false;
998 
999 	}
1000 
1001 	public void removeIssueRelation(Integer relationId, Integer userId) {
1002 		IssueRelation issueRelation = getIssueRelationDAO().findByPrimaryKey(
1003 				relationId);
1004 		Integer issueId = issueRelation.getIssue().getId();
1005 
1006 		Integer relatedIssueId = issueRelation.getRelatedIssue().getId();
1007 
1008 		Integer matchingRelationId = issueRelation.getMatchingRelationId();
1009 
1010 		if (matchingRelationId != null) {
1011 			IssueActivity activity = new IssueActivity();
1012 			activity
1013 			.setActivityType(org.itracker.model.IssueActivityType.RELATION_REMOVED);
1014 			activity.setDescription(ITrackerResources.getString(
1015 					"itracker.activity.relation.removed", issueId.toString()));
1016 			// FIXME need to fix the commented code and save
1017 			// activity.setIssue(relatedIssueId);
1018 			// activity.setUser(userId);
1019 			// IssueRelationDAO.remove(matchingRelationId);
1020 		}
1021 
1022 		IssueActivity activity = new IssueActivity();
1023 		activity
1024 		.setActivityType(org.itracker.model.IssueActivityType.RELATION_REMOVED);
1025 		activity.setDescription(ITrackerResources
1026 				.getString("itracker.activity.relation.removed", relatedIssueId
1027 						.toString()));
1028 		// activity.setIssue(issueId);
1029 		// activity.setUser(userId);
1030 		// irHome.remove(relationId);
1031 		// need to save
1032 
1033 		getIssueRelationDAO().delete(issueRelation);
1034 	}
1035 
1036 	public boolean assignIssue(Integer issueId, Integer userId) {
1037 		return assignIssue(issueId, userId, userId);
1038 	}
1039 
1040 	/**
1041 	 * only use for updating issue from actions..
1042 	 */
1043 	public boolean assignIssue(Integer issueId, Integer userId,
1044 			Integer assignedByUserId) {
1045 
1046 		return assignIssue(getIssueDAO().findByPrimaryKey(issueId),
1047 				getUserDAO().findByPrimaryKey(userId), getUserDAO()
1048 				.findByPrimaryKey(assignedByUserId), true);
1049 	}
1050 
1051 	/**
1052 	 * Only for use
1053 	 * 
1054 	 * @param issueId
1055 	 * @param userId
1056 	 * @param assignedByUserId
1057 	 * @param save
1058 	 *            save issue and send notification
1059 	 * @return
1060 	 */
1061 	private boolean assignIssue(Issue issue, User user, User assignedByUser,
1062 			final boolean save) {
1063 
1064 		if (issue.getOwner() == user
1065 				|| (null != issue.getOwner() && issue.getOwner().equals(user))) {
1066 			// nothing to do.
1067 			if (logger.isDebugEnabled()) {
1068 				logger.debug("assignIssue: attempted to reassign " + issue
1069 						+ " to current owner " + user);
1070 			}
1071 			return false;
1072 		}
1073 
1074 		if (null == user) {
1075 			if (logger.isInfoEnabled()) {
1076 				logger.info("assignIssue: call to unasign " + issue);
1077 			}
1078 
1079 			return unassignIssue(issue, assignedByUser, save);
1080 		}
1081 
1082 		if (logger.isInfoEnabled()) {
1083 			logger.info("assignIssue: assigning " + issue + " to " + user);
1084 		}
1085 
1086 		User currOwner = issue.getOwner();
1087 
1088 		if (!user.equals(currOwner)) {
1089 			if (currOwner != null
1090 					&& !notificationService.hasIssueNotification(issue,
1091 							currOwner.getId(), Role.IP)) {
1092 				// Notification notification = new Notification();
1093 				Notification notification = new Notification(currOwner, issue,
1094 						Role.IP);
1095 				if (save) {
1096 					notificationService.addIssueNotification(notification);
1097 				} else {
1098 					issue.getNotifications().add(notification);
1099 				}
1100 			}
1101 
1102 			IssueActivity activity = new IssueActivity();
1103 			activity
1104 			.setActivityType(org.itracker.model.IssueActivityType.OWNER_CHANGE);
1105 			activity.setDescription((currOwner == null ? "["
1106 					+ ITrackerResources
1107 					.getString("itracker.web.generic.unassigned") + "]"
1108 					: currOwner.getLogin())
1109 					+ " "
1110 					+ ITrackerResources.getString("itracker.web.generic.to")
1111 					+ " " + user.getLogin());
1112 			activity.setUser(assignedByUser);
1113 			activity.setIssue(issue);
1114 			issue.getActivities().add(activity);
1115 
1116 			issue.setOwner(user);
1117 
1118 			if (logger.isDebugEnabled()) {
1119 				logger.debug("assignIssue: current status: "
1120 						+ issue.getStatus());
1121 			}
1122 			if (issue.getStatus() < IssueUtilities.STATUS_ASSIGNED) {
1123 				issue.setStatus(IssueUtilities.STATUS_ASSIGNED);
1124 				if (logger.isDebugEnabled()) {
1125 					logger.debug("assignIssue: new status set to "
1126 							+ issue.getStatus());
1127 				}
1128 			}
1129 
1130 			// send assignment notification
1131 			// TODO: configurationService should be set from context
1132 			if (save) {
1133 				if (logger.isDebugEnabled()) {
1134 					logger.debug("assignIssue: saving re-assigned issue");
1135 				}
1136 				getIssueDAO().saveOrUpdate(issue);
1137 				notificationService.sendNotification(issue, Type.ASSIGNED,
1138 						ServletContextUtils.getItrackerServices()
1139 						.getConfigurationService().getSystemBaseURL());
1140 
1141 			}
1142 		}
1143 		return true;
1144 
1145 	}
1146 
1147 	/**
1148 	 * 
1149 	 * @param issue
1150 	 * @param unassignedByUser
1151 	 * @param save
1152 	 *            save issue and send notification
1153 	 * @return
1154 	 */
1155 	private boolean unassignIssue(Issue issue, User unassignedByUser,
1156 			boolean save) {
1157 		if (logger.isDebugEnabled()) {
1158 			logger.debug("unassignIssue: " + issue);
1159 		}
1160 		if (issue.getOwner() != null) {
1161 
1162 			if (logger.isDebugEnabled()) {
1163 				logger.debug("unassignIssue: unassigning from "
1164 						+ issue.getOwner());
1165 			}
1166 			if (!notificationService.hasIssueNotification(issue, issue
1167 					.getOwner().getId(), Role.CONTRIBUTER)) {
1168 				// Notification notification = new Notification();
1169 				Notification notification = new Notification(issue.getOwner(),
1170 						issue, Role.CONTRIBUTER);
1171 				if (save) {
1172 					notificationService.addIssueNotification(notification);
1173 				} else {
1174 					issue.getNotifications().add(notification);
1175 				}
1176 			}
1177 			IssueActivity activity = new IssueActivity(issue, unassignedByUser,
1178 					IssueActivityType.OWNER_CHANGE);
1179 			activity
1180 			.setDescription(issue.getOwner().getLogin()
1181 					+ " "
1182 					+ ITrackerResources
1183 					.getString("itracker.web.generic.to")
1184 					+ " ["
1185 					+ ITrackerResources
1186 					.getString("itracker.web.generic.unassigned")
1187 					+ "]");
1188 
1189 			issue.setOwner(null);
1190 
1191 			if (issue.getStatus() >= IssueUtilities.STATUS_ASSIGNED) {
1192 				issue.setStatus(IssueUtilities.STATUS_UNASSIGNED);
1193 			}
1194 			if (save) {
1195 				if (logger.isDebugEnabled()) {
1196 					logger.debug("unassignIssue: saving unassigned issue..");
1197 				}
1198 				getIssueDAO().saveOrUpdate(issue);
1199 				notificationService.sendNotification(issue, Type.ASSIGNED,
1200 						ServletContextUtils.getItrackerServices()
1201 						.getConfigurationService().getSystemBaseURL());
1202 			}
1203 		}
1204 
1205 		return true;
1206 	}
1207 
1208 	/**
1209 	 * System-Update an issue, adds the action to the issue and updates the
1210 	 * issue
1211 	 */
1212 	public Issue systemUpdateIssue(Issue updateissue, Integer userId)
1213 	throws ProjectException {
1214 
1215 		IssueActivity activity = new IssueActivity();
1216 		activity.setActivityType(IssueActivityType.SYSTEM_UPDATE);
1217 		activity.setDescription(ITrackerResources
1218 				.getString("itracker.activity.system.status"));
1219 		ArrayList<IssueActivity> activities = new ArrayList<IssueActivity>();
1220 
1221 		activity.setIssue(updateissue);
1222 		activity.setUser(getUserDAO().findByPrimaryKey(userId));
1223 		updateissue.getActivities().add(activity);
1224 
1225 		Issue updated = updateIssue(updateissue, userId);
1226 		updated.getActivities().addAll(activities);
1227 		getIssueDAO().saveOrUpdate(updated);
1228 
1229 		return updated;
1230 	}
1231 
1232 	/*
1233 	 * public boolean addIssueActivity(IssueActivityModel model) {
1234 	 * 
1235 	 * Issue issue = ifHome.findByPrimaryKey(model.getIssueId());
1236 	 * 
1237 	 * User user = ufHome.findByPrimaryKey(model.getUserId());
1238 	 * 
1239 	 * //return addIssueActivity(model, issue, user); return
1240 	 * addIssueActivity(null, issue, user); }
1241 	 */
1242 
1243 	/*
1244 	 * public boolean addIssueActivity(IssueActivityModel model, Issue issue) {
1245 	 * 
1246 	 * User user = ufHome.findByPrimaryKey(model.getUserId());
1247 	 * 
1248 	 * return true;//addIssueActivity(model, issue, user); }
1249 	 */
1250 
1251 	/**
1252 	 * I think this entire method is useless - RJST TODO move to
1253 	 * {@link NotificationService}
1254 	 * 
1255 	 * @param model
1256 	 * @param issue
1257 	 * @param user
1258 	 * @return
1259 	 */
1260 	/*
1261 	 * public boolean addIssueActivity(IssueActivityBean model, Issue issue,
1262 	 * User user) {
1263 	 * 
1264 	 * IssueActivityBean activity = new IssueActivityBean();
1265 	 * 
1266 	 * //activity.setModel(model);
1267 	 * 
1268 	 * activity.setIssue(issue);
1269 	 * 
1270 	 * activity.setUser(user);
1271 	 * 
1272 	 * return true; }
1273 	 */
1274 
1275 	public void updateIssueActivityNotification(Integer issueId,
1276 			boolean notificationSent) {
1277 
1278 		if (issueId == null) {
1279 
1280 			return;
1281 
1282 		}
1283 
1284 		Collection<IssueActivity> activity = getIssueActivityDAO()
1285 		.findByIssueId(issueId);
1286 
1287 		for (Iterator<IssueActivity> iter = activity.iterator(); iter.hasNext();) {
1288 
1289 			((IssueActivity) iter.next()).setNotificationSent(notificationSent);
1290 
1291 		}
1292 
1293 	}
1294 
1295 	/**
1296 	 * Adds an attachment to an issue
1297 	 * 
1298 	 * @param model
1299 	 *            The attachment data
1300 	 * @param data
1301 	 *            The byte data
1302 	 */
1303 	public boolean addIssueAttachment(IssueAttachment attachment, byte[] data) {
1304 		Issue issue = attachment.getIssue();
1305 		// User user = attachment.getUser();
1306 
1307 		attachment.setFileName("attachment_issue_" + issue.getId() + "_"
1308 				+ attachment.getOriginalFileName());
1309 		attachment.setFileData((data == null ? new byte[0] : data));
1310 
1311 		// attachment.setIssue(issue);
1312 		// attachment.setUser(user);
1313 
1314 		// TODO: activity for adding attachment?
1315 		// IssueActivity activityAdd = new IssueActivity(attachment.getIssue(),
1316 		// user, IssueActivity.Type.ATTACHEMENT_ADDED)
1317 
1318 		if (logger.isDebugEnabled()) {
1319 			logger.debug("addIssueAttachment: adding attachment " + attachment);
1320 		}
1321 		// add attachment to issue
1322 		issue.getAttachments().add(attachment);
1323 		if (logger.isDebugEnabled()) {
1324 			logger.debug("addIssueAttachment: saving updated issue " + issue);
1325 		}
1326 		this.getIssueDAO().saveOrUpdate(issue);
1327 		return true;
1328 	}
1329 
1330 	public boolean setIssueAttachmentData(Integer attachmentId, byte[] data) {
1331 
1332 		if (attachmentId != null && data != null) {
1333 
1334 			IssueAttachment attachment = getIssueAttachmentDAO()
1335 			.findByPrimaryKey(attachmentId);
1336 
1337 			attachment.setFileData(data);
1338 
1339 			return true;
1340 
1341 		}
1342 
1343 		return false;
1344 
1345 	}
1346 
1347 	public boolean setIssueAttachmentData(String fileName, byte[] data) {
1348 
1349 		if (fileName != null && data != null) {
1350 
1351 			IssueAttachment attachment = getIssueAttachmentDAO()
1352 			.findByFileName(fileName);
1353 
1354 			attachment.setFileData(data);
1355 
1356 			return true;
1357 
1358 		}
1359 
1360 		return false;
1361 
1362 	}
1363 
1364 	/**
1365 	 * Removes a attachement (deletes it)
1366 	 * 
1367 	 * @param attachmentId
1368 	 *            the id of the <code>IssueAttachmentBean</code>
1369 	 */
1370 	public boolean removeIssueAttachment(Integer attachmentId) {
1371 
1372 		IssueAttachment attachementBean = this.getIssueAttachmentDAO()
1373 		.findByPrimaryKey(attachmentId);
1374 
1375 		getIssueAttachmentDAO().delete(attachementBean);
1376 
1377 		return true;
1378 	}
1379 
1380 	public Integer removeIssueHistoryEntry(Integer entryId, Integer userId) {
1381 
1382 		IssueHistory history = getIssueHistoryDAO().findByPrimaryKey(entryId);
1383 
1384 		if (history != null) {
1385 
1386 			history.setStatus(IssueUtilities.HISTORY_STATUS_REMOVED);
1387 
1388 			// moved date stuff to BaseHibernateDAO
1389 			// history.setLastModifiedDate(new Timestamp(new Date().getTime()));
1390 
1391 			IssueActivity activity = new IssueActivity();
1392 			activity
1393 			.setActivityType(org.itracker.model.IssueActivityType.REMOVE_HISTORY);
1394 			activity.setDescription(ITrackerResources
1395 					.getString("itracker.web.generic.entry")
1396 					+ " "
1397 					+ entryId
1398 					+ " "
1399 					+ ITrackerResources
1400 					.getString("itracker.web.generic.removed") + ".");
1401 
1402 			getIssueHistoryDAO().delete(history);
1403 
1404 			// need to fix this - RJST
1405 			// activity.setIssue(history.getIssue().getId());
1406 			// activity.setUser(userId);
1407 			return history.getIssue().getId();
1408 
1409 		}
1410 
1411 		return Integer.valueOf(-1);
1412 
1413 	}
1414 
1415 	public Project getIssueProject(Integer issueId) {
1416 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
1417 		Project project = issue.getProject();
1418 
1419 		return project;
1420 	}
1421 
1422 	public HashSet<Integer> getIssueComponentIds(Integer issueId) {
1423 
1424 		HashSet<Integer> componentIds = new HashSet<Integer>();
1425 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
1426 		Collection<Component> components = issue.getComponents();
1427 
1428 		for (Iterator<Component> iterator = components.iterator(); iterator
1429 		.hasNext();) {
1430 			componentIds.add(((Component) iterator.next()).getId());
1431 		}
1432 
1433 		return componentIds;
1434 
1435 	}
1436 
1437 	public HashSet<Integer> getIssueVersionIds(Integer issueId) {
1438 
1439 		HashSet<Integer> versionIds = new HashSet<Integer>();
1440 
1441 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
1442 
1443 		Collection<Version> versions = issue.getVersions();
1444 
1445 		for (Iterator<Version> iterator = versions.iterator(); iterator
1446 		.hasNext();) {
1447 
1448 			versionIds.add(((Version) iterator.next()).getId());
1449 
1450 		}
1451 
1452 		return versionIds;
1453 
1454 	}
1455 
1456 	public List<IssueActivity> getIssueActivity(Integer issueId) {
1457 
1458 		int i = 0;
1459 
1460 		Collection<IssueActivity> activity = getIssueActivityDAO()
1461 		.findByIssueId(issueId);
1462 
1463 		IssueActivity[] activityArray = new IssueActivity[activity.size()];
1464 
1465 		for (Iterator<IssueActivity> iterator = activity.iterator(); iterator
1466 		.hasNext(); i++) {
1467 
1468 			activityArray[i] = ((IssueActivity) iterator.next());
1469 
1470 		}
1471 
1472 		return Arrays.asList(activityArray);
1473 
1474 	}
1475 
1476 	/**
1477 	 * TODO move to {@link NotificationService} ?
1478 	 */
1479 	public List<IssueActivity> getIssueActivity(Integer issueId,
1480 			boolean notificationSent) {
1481 
1482 		int i = 0;
1483 
1484 		Collection<IssueActivity> activity = getIssueActivityDAO()
1485 		.findByIssueIdAndNotification(issueId, notificationSent);
1486 
1487 		IssueActivity[] activityArray = new IssueActivity[activity.size()];
1488 
1489 		for (Iterator<IssueActivity> iterator = activity.iterator(); iterator
1490 		.hasNext(); i++) {
1491 
1492 			activityArray[i] = ((IssueActivity) iterator.next());
1493 
1494 		}
1495 
1496 		return Arrays.asList(activityArray);
1497 
1498 	}
1499 
1500 	public Long getAllIssueAttachmentCount() {
1501 		return getIssueAttachmentDAO().countAll().longValue();
1502 	}
1503 
1504 	/**
1505 	 * @deprecated do not use this due to expensive memory use! use explicit
1506 	 *             hsqldb queries instead.
1507 	 */
1508 	public List<IssueAttachment> getAllIssueAttachments() {
1509 		logger.warn("getAllIssueAttachments: use of deprecated API");
1510 		if (logger.isDebugEnabled()) {
1511 			logger.debug("getAllIssueAttachments: stacktrace was",
1512 					new RuntimeException());
1513 		}
1514 
1515 		List<IssueAttachment> attachments = getIssueAttachmentDAO().findAll();
1516 
1517 		return attachments;
1518 	}
1519 
1520 	public IssueAttachment getIssueAttachment(Integer attachmentId) {
1521 		IssueAttachment attachment = getIssueAttachmentDAO().findByPrimaryKey(
1522 				attachmentId);
1523 
1524 		return attachment;
1525 
1526 	}
1527 
1528 	public byte[] getIssueAttachmentData(Integer attachmentId) {
1529 
1530 		byte[] data;
1531 
1532 		IssueAttachment attachment = getIssueAttachmentDAO().findByPrimaryKey(
1533 				attachmentId);
1534 
1535 		data = attachment.getFileData();
1536 
1537 		return data;
1538 
1539 	}
1540 
1541 	public int getIssueAttachmentCount(Integer issueId) {
1542 
1543 		int i = 0;
1544 
1545 		Issue issue = getIssueDAO().findByPrimaryKey(issueId);
1546 
1547 		Collection<IssueAttachment> attachments = issue.getAttachments();
1548 
1549 		i = attachments.size();
1550 
1551 		return i;
1552 
1553 	}
1554 
1555 	/**
1556 	 * 
1557 	 * Returns the latest issue history entry for a particular issue.
1558 	 * 
1559 	 * 
1560 	 * 
1561 	 * @param issueId
1562 	 * 
1563 	 *            the id of the issue to return the history entry for.
1564 	 * 
1565 	 * @return the latest IssueHistory, or null if no entries could be found
1566 	 */
1567 	public IssueHistory getLastIssueHistory(Integer issueId) {
1568 
1569 		List<IssueHistory> history = getIssueHistoryDAO()
1570 		.findByIssueId(issueId);
1571 
1572 		if (null != history && history.size() > 0) {
1573 			// sort ascending by id
1574 			Collections.sort(history, AbstractEntity.ID_COMPARATOR);
1575 			// return last entry in list
1576 			return history.get(history.size() - 1);
1577 		}
1578 
1579 		return null;
1580 
1581 	}
1582 
1583 	public int getOpenIssueCountByProjectId(Integer projectId) {
1584 
1585 		Collection<Issue> issues = getIssueDAO().findByProjectAndLowerStatus(
1586 				projectId, IssueUtilities.STATUS_RESOLVED);
1587 
1588 		return issues.size();
1589 
1590 	}
1591 
1592 	public int getResolvedIssueCountByProjectId(Integer projectId) {
1593 
1594 		Collection<Issue> issues = getIssueDAO().findByProjectAndHigherStatus(
1595 				projectId, IssueUtilities.STATUS_RESOLVED);
1596 
1597 		return issues.size();
1598 
1599 	}
1600 
1601 	public int getTotalIssueCountByProjectId(Integer projectId) {
1602 
1603 		Collection<Issue> issues = getIssueDAO().findByProject(projectId);
1604 
1605 		return issues.size();
1606 
1607 	}
1608 
1609 	public Date getLatestIssueDateByProjectId(Integer projectId) {
1610 
1611 		return getIssueDAO().latestModificationDate(projectId);
1612 
1613 	}
1614 
1615 	public boolean canViewIssue(Integer issueId, User user) {
1616 
1617 		Issue issue = getIssue(issueId);
1618 
1619 		Map<Integer, Set<PermissionType>> permissions = getUserDAO()
1620 		.getUsersMapOfProjectsAndPermissionTypes(user);
1621 
1622 		return IssueUtilities.canViewIssue(issue, user.getId(), permissions);
1623 
1624 	}
1625 
1626 	public boolean canViewIssue(Issue issue, User user) {
1627 
1628 		Map<Integer, Set<PermissionType>> permissions = getUserDAO()
1629 		.getUsersMapOfProjectsAndPermissionTypes(user);
1630 
1631 		return IssueUtilities.canViewIssue(issue, user.getId(), permissions);
1632 
1633 	}
1634 
1635 	private UserDAO getUserDAO() {
1636 		return userDAO;
1637 	}
1638 
1639 	private IssueDAO getIssueDAO() {
1640 		return issueDAO;
1641 	}
1642 
1643 	private ProjectDAO getProjectDAO() {
1644 		return projectDAO;
1645 	}
1646 
1647 	private IssueActivityDAO getIssueActivityDAO() {
1648 		return issueActivityDAO;
1649 	}
1650 
1651 	private VersionDAO getVersionDAO() {
1652 		return this.versionDAO;
1653 	}
1654 
1655 	private ComponentDAO getComponentDAO() {
1656 		return this.componentDAO;
1657 	}
1658 
1659 	private CustomFieldDAO getCustomFieldDAO() {
1660 		return customFieldDAO;
1661 	}
1662 
1663 	private IssueHistoryDAO getIssueHistoryDAO() {
1664 		return issueHistoryDAO;
1665 	}
1666 
1667 	private IssueRelationDAO getIssueRelationDAO() {
1668 		return issueRelationDAO;
1669 	}
1670 
1671 	private IssueAttachmentDAO getIssueAttachmentDAO() {
1672 		return issueAttachmentDAO;
1673 	}
1674 
1675 	/**
1676 	 * get total size of all attachments in database
1677 	 */
1678 	public Long getAllIssueAttachmentSize() {
1679 
1680 		return getIssueAttachmentDAO().totalAttachmentsSize().longValue() / 1024;
1681 
1682 	}
1683 
1684 	public List<Issue> searchIssues(IssueSearchQuery queryModel, User user,
1685 			Map<Integer, Set<PermissionType>> userPermissions)
1686 			throws IssueSearchException {
1687 		return getIssueDAO().query(queryModel, user, userPermissions);
1688 	}
1689 
1690 	public Long totalSystemIssuesAttachmentSize() {
1691 		return getIssueAttachmentDAO().totalAttachmentsSize();
1692 	}
1693 
1694 }