package models.services;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import com.fasterxml.jackson.databind.node.ObjectNode;
import managers.NotificationManager;
import models.Account;
import models.Notification;
import models.base.BaseNotifiable;
import models.base.INotifiable;
import models.enums.EmailNotifications;
import play.Logger;
import play.db.jpa.JPAApi;
import play.libs.Json;
import scala.concurrent.duration.Duration;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* This class handles the notification system.
*/
@Singleton
public class NotificationService {
final Logger.ALogger LOG = Logger.of(NotificationService.class);
WebSocketService webSocketService;
EmailService email;
NotificationManager notificationManager;
ActorSystem system;
JPAApi jpaApi;
/**
* Private constructor for singleton instance
*/
@Inject
public NotificationService(EmailService email, NotificationManager notificationManager, JPAApi jpaApi) {
this.email = email;
this.webSocketService = webSocketService;
this.notificationManager = notificationManager;
this.system = ActorSystem.create();
this.jpaApi = jpaApi;
}
/**
* Creates one or more notifications by the notifiable instance.
* The creation is done asynchronized using the Akka subsystem to be non-blocking.
*
* @param notifiable Notifiable instance, to retrieve the required notification data
*/
public void createNotification(final INotifiable notifiable) {
new Thread(() -> {
new NotificationRunnable(notifiable).run();
}).start();
}
/**
* Overloaded method createNotification() with notification type. The notification type
* is an important information for nearly every notification, as it determines the
* correct template and eventually also logic in methods like getRecipients() of the
* notification.
*
* @param notifiable Notifiable instance, to retrieve the required notification data
* @param notificationType Type of this notification
*/
public void createNotification(INotifiable notifiable, final String notificationType) {
if (notifiable instanceof BaseNotifiable) {
((BaseNotifiable)notifiable).type = notificationType;
}
this.createNotification(notifiable);
}
/**
* Sub-Class to implement a Runnable interface for notification creation.
*/
public class NotificationRunnable implements Runnable {
private INotifiable notifiable;
private NotificationRunnable self;
/**
* Constructor.
*
* @param notifiable Notifiable instance, to retrieve the required notification data
*/
public NotificationRunnable(final INotifiable notifiable) {
this.notifiable = notifiable;
this.self = this;
}
@Override
public void run() {
jpaApi.withTransaction(() -> {
List<Account> recipients = notifiable.getRecipients();
// if no recipients, abort
if (recipients == null || recipients.size() == 0) {
return;
}
// run through all recipients
for (Account recipient : recipients) {
// if sender == recipient, it is not necessary to create a notification -> continue
if (recipient.equals(notifiable.getSender())) {
continue;
}
// create new notification and persist in database
Notification notification = notifiable.getNotification(recipient);
notification.isRead = false;
notification.isSent = false;
notification.recipient = recipient;
notification.sender = notifiable.getSender();
notification.referenceId = notifiable.getReference().id;
notification.referenceType = notifiable.getReference().getClass().getSimpleName();
notification.targetUrl = notifiable.getTargetUrl();
try {
// render notification content
notification.rendered = notifiable.render(notification);
// if no ID is set already, persist new instance, otherwise update given instance
if (notification.id == null) {
notificationManager.create(notification);
LOG.info("Created new notification for user: " + recipient.id.toString());
} else {
notificationManager.update(notification);
LOG.info("Updated notification (ID: " + notification.id.toString()
+ ") for user: " + recipient.id.toString()
);
}
self.webSocketPush(notification);
self.handleMail(notification);
} catch (Exception e) {
LOG.error("Could not render notification. Notification will not be stored in DB" +
" nor will the user be notified in any way." + e.getMessage()
);
}
}
});
}
/**
* Pushes the new notification to the recipient if he is currently online.
*
* @param notification Notification
*/
public void webSocketPush(final Notification notification) {
/**
ActorRef recipientActor = webSocketService.getActorForAccount(notification.recipient);
// continue if recipientActor is instance (he is currently online)
if (recipientActor != null) {
ObjectNode node = webSocketService
.successResponseTemplate(WebSocketService.WS_METHOD_RECEIVE_NOTIFICATION);
node.put("notification", notification.getAsJson());
node.put("unreadCount", notificationManager.countUnreadNotificationsForAccountId(notification.recipient.id));
recipientActor.tell(Json.toJson(node), null);
}
*/
}
/**
* Sends mail to recipient, if he wishes to be notified via mail immediately
* and notification is currently unsent/unread.
*
* @param notification Notification
*/
public void handleMail(final Notification notification) {
if (notification.recipient.emailNotifications == EmailNotifications.IMMEDIATELY_ALL
&& !notification.isSent
&& !notification.isRead
) {
// since hibernate persist and update contradictions
// schedule another process for email handling in 1 second from now on
system.scheduler().scheduleOnce(
Duration.create(1, TimeUnit.SECONDS),
() -> { email.sendNotificationEmail(notification); },
system.dispatcher()
);
}
}
}
}