NotificationServiceImpl.java
/*
* This software was designed and created by Jason Carroll.
* Copyright (c) 2002, 2003, 2004 Jason Carroll.
* The author can be reached at jcarroll@cowsultants.com
* ITracker website: http://www.cowsultants.com
* ITracker forums: http://www.cowsultants.com/phpBB/index.php
*
* This program is free software; you can redistribute it and/or modify
* it only under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package org.itracker.services.implementations;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.itracker.core.resources.ITrackerResources;
import org.itracker.model.*;
import org.itracker.model.Notification.Role;
import org.itracker.model.Notification.Type;
import org.itracker.model.util.IssueUtilities;
import org.itracker.model.util.ProjectUtilities;
import org.itracker.model.util.UserUtilities;
import org.itracker.persistence.dao.IssueActivityDAO;
import org.itracker.persistence.dao.IssueDAO;
import org.itracker.persistence.dao.NotificationDAO;
import org.itracker.services.EmailService;
import org.itracker.services.IssueService;
import org.itracker.services.NotificationService;
import org.itracker.services.ProjectService;
import org.itracker.util.HTMLUtilities;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.mail.internet.InternetAddress;
import java.net.MalformedURLException;
import java.util.*;
public class NotificationServiceImpl implements NotificationService, ApplicationContextAware {
public static final Integer DEFAULT_ISSUE_AGE = 30;
private EmailService emailService;
private NotificationDAO notificationDao;
private ProjectService projectService;
private IssueActivityDAO issueActivityDao;
private IssueDAO issueDao;
private String issueServiceName;
private static final Logger logger = Logger
.getLogger(NotificationServiceImpl.class);
private IssueService issueService;
private ApplicationContext applicationContext;
public NotificationServiceImpl() {
this.emailService = null;
this.projectService = null;
this.notificationDao = null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public NotificationServiceImpl(EmailService emailService,
ProjectService projectService, NotificationDAO notificationDao, IssueActivityDAO issueActivityDao, IssueDAO issueDao, IssueService issueService) {
this();
this.setEmailService(emailService);
this.setProjectService(projectService);
this.setNotificationDao(notificationDao);
this.setIssueActivityDao(issueActivityDao);
this.setIssueDao(issueDao);
this.setIssueService(issueService);
}
public void sendNotification(Notification notification, Type type,
String url) {
if (logger.isDebugEnabled()) {
logger.debug("sendNotification: called with notification: "
+ notification + ", type: " + url + ", url: " + url);
}
if (null == notification) {
throw new IllegalArgumentException("notification must not be null");
}
if (null == this.emailService || null == this.notificationDao) {
throw new IllegalStateException("service not initialized yet");
}
if (type == Type.SELF_REGISTER) {
this.handleSelfRegistrationNotification(notification.getUser()
.getLogin(), notification.getUser().getEmailAddress(), notification.getUser().getPreferences().getUserLocale(), url);
} else {
handleIssueNotification(notification.getIssue(), type, url);
}
}
public void sendNotification(Issue issue, Type type, String baseURL) {
if (logger.isDebugEnabled()) {
logger.debug("sendNotification: called with issue: " + issue
+ ", type: " + type + ", baseURL: " + baseURL);
}
handleIssueNotification(issue, type, baseURL);
}
public void setEmailService(EmailService emailService) {
if (null == emailService)
throw new IllegalArgumentException("email service must not be null");
if (null != this.emailService) {
throw new IllegalStateException("email service allready set");
}
this.emailService = emailService;
}
private void handleSelfRegistrationNotification(String login,
InternetAddress toAddress, String locale, String url) {
if (logger.isDebugEnabled()) {
logger
.debug("handleSelfRegistrationNotification: called with login: "
+ login
+ ", toAddress"
+ toAddress
+ ", url: "
+ url);
}
try {
if (toAddress != null && !"".equals(toAddress.getAddress())) {
String subject = ITrackerResources
.getString("itracker.email.selfreg.subject", locale);
String msgText = ITrackerResources.getString(
"itracker.email.selfreg.body", locale, new Object[]{login,
url + "/login.do"});
emailService.sendEmail(toAddress, subject, msgText);
} else {
throw new IllegalArgumentException(
"To-address must be set for self registration notification.");
}
} catch (RuntimeException e) {
logger.error("failed to handle self registration notification for "
+ toAddress, e);
throw e;
}
}
/**
* Method for internal sending of a notification of specific type.
*/
private void handleIssueNotification(Issue issue, Type type, String url) {
if (logger.isDebugEnabled()) {
logger.debug("handleIssueNotification: called with issue: " + issue
+ ", type: " + type + "url: " + url);
}
this.handleLocalizedIssueNotification(issue, type, url, null, null);
}
/**
* Method for internal sending of a notification of specific type.
*/
private void handleLocalizedIssueNotification(final Issue issue, final Type type, final String url,
final InternetAddress[] recipients, Integer lastModifiedDays) {
try {
if (logger.isDebugEnabled()) {
logger
.debug("handleLocalizedIssueNotification: running as thread, called with issue: "
+ issue
+ ", type: "
+ type
+ "url: "
+ url
+ ", recipients: "
+ (null == recipients ? "<null>" : String
.valueOf(Arrays.asList(recipients)))
+ ", lastModifiedDays: " + lastModifiedDays);
}
final Integer notModifiedSince;
if (lastModifiedDays == null || lastModifiedDays < 0) {
notModifiedSince = DEFAULT_ISSUE_AGE;
} else {
notModifiedSince = lastModifiedDays;
}
try {
if (logger.isDebugEnabled()) {
logger
.debug("handleLocalizedIssueNotification.run: running as thread, called with issue: "
+ issue
+ ", type: "
+ type
+ "url: "
+ url
+ ", recipients: "
+ (null == recipients ? "<null>" : String
.valueOf(Arrays.asList(recipients)))
+ ", notModifiedSince: " + notModifiedSince);
}
final List<Notification> notifications;
if (issue == null) {
logger
.warn("handleLocalizedIssueNotification: issue was null. Notification will not be handled");
return;
}
Map<InternetAddress, Locale> localeMapping;
if (recipients == null) {
notifications = this.getIssueNotifications(issue);
localeMapping = new Hashtable<>(notifications.size());
Iterator<Notification> it = notifications.iterator();
User currentUser;
while (it.hasNext()) {
currentUser = it.next().getUser();
if (null != currentUser
&& null != currentUser.getEmailAddress()
&& null != currentUser.getEmail()
&& (!localeMapping.keySet()
.contains(currentUser.getEmailAddress()))) {
try {
localeMapping.put(currentUser.getEmailAddress(), ITrackerResources.getLocale(currentUser.getPreferences().getUserLocale()));
} catch (RuntimeException re) {
localeMapping.put(currentUser.getEmailAddress(), ITrackerResources.getLocale());
}
}
}
} else {
localeMapping = new Hashtable<>(1);
Locale locale = ITrackerResources.getLocale();
for (InternetAddress internetAddress : Arrays.asList(recipients)) {
localeMapping.put(internetAddress, locale);
}
}
this.handleNotification(issue, type, localeMapping, url, notModifiedSince);
} catch (Exception e) {
logger.error("run: failed to process notification", e);
}
} catch (Exception e) {
logger
.error(
"handleLocalizedIssueNotification: unexpected exception caught, throwing runtime exception",
e);
throw new RuntimeException(e);
}
}
@Override
public void sendReminder(Issue issue, User user, String baseURL, int issueAge) {
Map<InternetAddress, Locale> recipient = new HashMap<>(1);
recipient.put(user.getEmailAddress(), ITrackerResources.getLocale(user.getPreferences().getUserLocale()));
handleNotification(issue, Type.ISSUE_REMINDER, recipient, baseURL, issueAge);
}
/**
* Send notifications to mapped addresses by locale.
*/
private void handleNotification(Issue issue, Type type, Map<InternetAddress, Locale> recipientsLocales, final String url, Integer notModifiedSince) {
Set<InternetAddress> recipients;
Map<Locale, Set<InternetAddress>> localeRecipients = new Hashtable<>();
List<Component> components = issue.getComponents();
List<IssueActivity> activity = getIssueService().getIssueActivity(
issue.getId(), false);
IssueHistory history;
history = getIssueService().getLastIssueHistory(issue.getId());
StringBuilder recipientsString = new StringBuilder();
if (logger.isDebugEnabled() && null != history) {
logger.debug("handleIssueNotification: got most recent history: "
+ history
+ " ("
+ history.getDescription()
+ ")");
}
for (InternetAddress internetAddress : recipientsLocales.keySet()) {
recipientsString.append("\n ");
recipientsString.append(internetAddress.getPersonal());
if (localeRecipients.keySet().contains(recipientsLocales.get(internetAddress))) {
localeRecipients.get(recipientsLocales.get(internetAddress)).add(internetAddress);
} else {
Set<InternetAddress> addresses = new HashSet<>();
addresses.add(internetAddress);
localeRecipients.put(recipientsLocales.get(internetAddress), addresses);
}
}
Iterator<Locale> localesIt = localeRecipients.keySet().iterator();
try {
while (localesIt.hasNext()) {
Locale currentLocale = localesIt.next();
recipients = localeRecipients.get(currentLocale);
if (recipients.size() > 0) {
String subject;
if (type == Type.CREATED) {
subject = ITrackerResources.getString(
"itracker.email.issue.subject.created",
currentLocale,
new Object[]{issue.getId(),
issue.getProject().getName()});
} else if (type == Type.ASSIGNED) {
subject = ITrackerResources.getString(
"itracker.email.issue.subject.assigned",
currentLocale,
new Object[]{issue.getId(),
issue.getProject().getName()});
} else if (type == Type.CLOSED) {
subject = ITrackerResources.getString(
"itracker.email.issue.subject.closed",
currentLocale,
new Object[]{issue.getId(),
issue.getProject().getName()});
} else if (type == Type.ISSUE_REMINDER) {
subject = ITrackerResources.getString(
"itracker.email.issue.subject.reminder",
currentLocale,
new Object[]{issue.getId(),
issue.getProject().getName(),
notModifiedSince});
} else {
subject = ITrackerResources.getString(
"itracker.email.issue.subject.updated",
currentLocale,
new Object[]{issue.getId(),
issue.getProject().getName()});
}
String activityString;
String componentString = "";
StringBuilder sb = new StringBuilder();
if (activity.size() == 0) {
sb.append("-");
} else {
for (IssueActivity anActivity : activity) {
sb.append("\n ").append(
IssueUtilities.getActivityName(anActivity
.getActivityType(), currentLocale)).append(": ").append(
anActivity.getDescription());
}
}
sb.append("\n");
activityString = sb.toString();
for (int i = 0; i < components.size(); i++) {
componentString += (i != 0 ? ", " : "")
+ components.get(i).getName();
}
final String owner = IssueUtilities.getOwnerName(issue.getOwner(), currentLocale);
final User hUser = null == history ? null : history.getUser();
final String historyUser = (null != hUser) ? hUser.getFullName()
: ITrackerResources.getString("itracker.web.generic.notapplicable", currentLocale);
final String historyText = (history == null ? "-"
: HTMLUtilities
.removeMarkup(history
.getDescription()));
final String status =
IssueUtilities.getStatusName(issue
.getStatus(), currentLocale);
final String msgText;
if (type == Type.ISSUE_REMINDER) {
msgText = ITrackerResources
.getString(
"itracker.email.issue.body.reminder",
currentLocale,
new Object[]{
IssueUtilities.getIssueURL(issue, url).toExternalForm(),
issue.getProject().getName(),
issue.getDescription(),
IssueUtilities.getStatusName(issue
.getStatus(), currentLocale),
IssueUtilities
.getSeverityName(issue
.getSeverity(), currentLocale),
owner,
componentString,
historyUser,
historyText,
notModifiedSince,
activityString});
} else {
String resolution = (issue.getResolution() == null ? ""
: issue.getResolution());
if (!resolution.equals("")
&& ProjectUtilities
.hasOption(
ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS,
issue.getProject().getOptions())) {
resolution = IssueUtilities.getResolutionName(
resolution, currentLocale);
}
msgText = ITrackerResources
.getString(
"itracker.email.issue.body."
+ (type == Type.CREATED ? "created" : "standard"),
currentLocale,
new Object[]{
url + "/module-projects/view_issue.do?id=" + issue.getId(),
issue.getProject().getName(),
issue.getDescription(),
status,
resolution,
IssueUtilities
.getSeverityName(issue
.getSeverity(), currentLocale),
owner,
componentString,
historyUser,
historyText,
activityString,
recipientsString});
}
if (logger.isInfoEnabled()) {
logger.info("handleNotification: sending notification for " + issue + " (" + type + ") to " + currentLocale + "-users (" + recipients + ")");
}
for (InternetAddress iadr : recipients) {
emailService.sendEmail(iadr, subject, msgText);
}
if (logger.isDebugEnabled()) {
logger.debug("handleNotification: sent notification for " + issue
+ ": " + subject + "\n " + msgText);
}
}
updateIssueActivityNotification(issue, true);
if (logger.isDebugEnabled()) {
logger.debug("handleNotification: sent notification for locales " + localeRecipients.keySet() + " recipients: " + localeRecipients.values());
}
}
} catch (RuntimeException e) {
logger.error("handleNotification: failed to notify: " + issue + " (locales: " + localeRecipients.keySet() + ")", e);
} catch (MalformedURLException e) {
logger.error("handleNotification: URL was not well-formed", e);
}
}
private IssueService getIssueService() {
if (null == issueService) {
setIssueService((IssueService) applicationContext.getBean("issueService"));
}
return issueService;
}
public void setIssueService(IssueService issueService) {
this.issueService = issueService;
}
public void updateIssueActivityNotification(Issue issue,
Boolean notificationSent) {
if (logger.isDebugEnabled()) {
logger.debug("updateIssueActivityNotification: called with "
+ issue + ", notificationSent: " + notificationSent);
}
Collection<IssueActivity> activities = getIssueActivityDao()
.findByIssueId(issue.getId());
for (IssueActivity activity : activities) {
activity.setNotificationSent(notificationSent);
}
}
/**
*/
public boolean addIssueNotification(Notification notification) {
if (logger.isDebugEnabled()) {
logger.debug("addIssueNotification: called with notification: "
+ notification);
}
Issue issue = notification.getIssue();
if (!issue.getNotifications().contains(notification)) {
if (notification.getCreateDate() == null) {
notification.setCreateDate(new Date());
}
if (notification.getLastModifiedDate() == null) {
notification.setLastModifiedDate(new Date());
}
getNotificationDao().save(notification);
issue.getNotifications().add(notification);
getIssueDao().merge(issue);
return true;
}
if (logger.isDebugEnabled()) {
logger.debug("addIssueNotification: attempted to add duplicate notification " + notification + " for issue: " + issue);
}
return false;
}
/**
*
*/
public List<Notification> getIssueNotifications(Issue issue,
boolean primaryOnly, boolean activeOnly) {
if (logger.isDebugEnabled()) {
logger.debug("getIssueNotifications: called with issue: " + issue
+ ", primaryOnly: " + primaryOnly + ", activeOnly: "
+ activeOnly);
}
List<Notification> issueNotifications = new ArrayList<>();
if (issue == null) {
logger.warn("getIssueNotifications: no issue, throwing exception");
throw new IllegalArgumentException("issue must not be null");
}
if (!primaryOnly) {
List<Notification> notifications = getNotificationDao()
.findByIssueId(issue.getId());
for (Notification notification : notifications) {
User notificationUser = notification.getUser();
if (!activeOnly
|| notificationUser.getStatus() == UserUtilities.STATUS_ACTIVE) {
issueNotifications.add(notification);
}
}
}
// Now add in other notifications like owner, creator, project owners,
// etc...
boolean hasOwner = false;
if (issue.getOwner() != null) {
User ownerModel = issue.getOwner();
if (ownerModel != null
&& (!activeOnly || ownerModel.getStatus() == UserUtilities.STATUS_ACTIVE)) {
issueNotifications.add(new Notification(ownerModel, issue,
Role.OWNER));
hasOwner = true;
}
}
if (!primaryOnly || !hasOwner) {
User creatorModel = issue.getCreator();
if (creatorModel != null
&& (!activeOnly || creatorModel.getStatus() == UserUtilities.STATUS_ACTIVE)) {
issueNotifications.add(new Notification(creatorModel,
issue, Role.CREATOR));
}
}
Project project = getProjectService().getProject(
issue.getProject().getId());
for (User projectOwner : project.getOwners()) {
if (projectOwner != null
&& (!activeOnly || projectOwner.getStatus() == UserUtilities.STATUS_ACTIVE)) {
issueNotifications.add(new Notification(projectOwner,
issue, Role.PO));
}
}
if (logger.isDebugEnabled()) {
logger.debug("getIssueNotifications: returning "
+ issueNotifications);
}
return issueNotifications;
}
public List<Notification> getIssueNotifications(Issue issue) {
if (logger.isDebugEnabled()) {
logger.debug("getIssueNotifications: called with: " + issue);
}
return this.getIssueNotifications(issue, false, true);
}
public List<Notification> getPrimaryIssueNotifications(Issue issue) {
if (logger.isDebugEnabled()) {
logger.debug("getPrimaryIssueNotifications: called with: " + issue);
}
return this.getIssueNotifications(issue, true, false);
}
public boolean hasIssueNotification(Issue issue, Integer userId) {
if (logger.isDebugEnabled()) {
logger.debug("hasIssueNotification: called with: " + issue
+ ", userId: " + userId);
}
return hasIssueNotification(issue, userId, Role.ANY);
}
@Override
public boolean hasIssueNotification(Issue issue, String login) {
return hasIssueNotification(issue, login, Role.ANY);
}
@Override
public boolean hasIssueNotification(Issue issue, String login, Role role) {
if (issue != null && StringUtils.isNotBlank(login)) {
List<Notification> notifications = getIssueNotifications(issue,
false, false);
for (Notification notification : notifications) {
if (role == Role.ANY || notification.getRole() == role) {
if (StringUtils.equals(login, notification.getUser().getLogin())) {
return true;
}
}
}
}
return false;
}
public boolean hasIssueNotification(Issue issue, Integer userId, Role role) {
if (issue != null && userId != null) {
List<Notification> notifications = getIssueNotifications(issue,
false, false);
for (Notification notification : notifications) {
if (role == Role.ANY || notification.getRole() == role) {
if (notification.getUser().getId().equals(userId)) {
return true;
}
}
}
}
return false;
}
public boolean removeIssueNotification(Integer notificationId) {
Notification notification = this.getNotificationDao().findById(
notificationId);
getNotificationDao().delete(notification);
return true;
}
public void sendNotification(Issue issue, Type type, String baseURL,
InternetAddress[] receipients, Integer lastModifiedDays) {
this.handleLocalizedIssueNotification(issue, type, baseURL, receipients,
lastModifiedDays);
}
/**
* @return the emailService
*/
public EmailService getEmailService() {
return emailService;
}
/**
* @return the notificationDao
*/
private NotificationDAO getNotificationDao() {
return notificationDao;
}
/**
* @return the projectService
*/
public ProjectService getProjectService() {
return projectService;
}
/**
* @param projectService the projectService to set
*/
public void setProjectService(ProjectService projectService) {
this.projectService = projectService;
}
/**
* @param notificationDao the notificationDao to set
*/
public void setNotificationDao(NotificationDAO notificationDao) {
if (null == notificationDao) {
throw new IllegalArgumentException(
"notification dao must not be null");
}
if (null != this.notificationDao) {
throw new IllegalStateException("notification dao allready set");
}
this.notificationDao = notificationDao;
}
/**
* @return the issueActivityDao
*/
public IssueActivityDAO getIssueActivityDao() {
return issueActivityDao;
}
/**
* @param issueActivityDao the issueActivityDao to set
*/
public void setIssueActivityDao(IssueActivityDAO issueActivityDao) {
this.issueActivityDao = issueActivityDao;
}
/**
* @return the issueDao
*/
public IssueDAO getIssueDao() {
return issueDao;
}
/**
* @param issueDao the issueDao to set
*/
public void setIssueDao(IssueDAO issueDao) {
this.issueDao = issueDao;
}
public String getIssueServiceName() {
return issueServiceName;
}
public void setIssueServiceName(String issueServiceName) {
this.issueServiceName = issueServiceName;
}
}