| 1 | package com.reallifedeveloper.common.application.notification; | |
| 2 | ||
| 3 | import static com.reallifedeveloper.common.domain.LogUtil.removeCRLF; | |
| 4 | ||
| 5 | import java.io.IOException; | |
| 6 | import java.util.ArrayList; | |
| 7 | import java.util.List; | |
| 8 | ||
| 9 | import org.slf4j.Logger; | |
| 10 | import org.slf4j.LoggerFactory; | |
| 11 | import org.springframework.transaction.annotation.Transactional; | |
| 12 | ||
| 13 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | |
| 14 | ||
| 15 | import com.reallifedeveloper.common.application.eventstore.EventStore; | |
| 16 | import com.reallifedeveloper.common.application.eventstore.StoredEvent; | |
| 17 | import com.reallifedeveloper.common.domain.ErrorHandling; | |
| 18 | ||
| 19 | /** | |
| 20 | * An application service to work with {@link NotificationLog NotificationLogs}. | |
| 21 | * | |
| 22 | * @author RealLifeDeveloper | |
| 23 | */ | |
| 24 | public class NotificationService { | |
| 25 | ||
| 26 | private static final Logger LOG = LoggerFactory.getLogger(NotificationService.class); | |
| 27 | ||
| 28 | private final EventStore eventStore; | |
| 29 | ||
| 30 | private final PublishedMessageTrackerRepository messageTrackerRepository; | |
| 31 | ||
| 32 | private final NotificationPublisher notificationPublisher; | |
| 33 | ||
| 34 | /** | |
| 35 | * Creates a new {@code NotificationService} that uses the given components. | |
| 36 | * | |
| 37 | * @param eventStore an event store for finding stored domain events | |
| 38 | * @param messageTrackerRepository a repository for keeping track of the last notification published | |
| 39 | * @param notificationPublisher a publisher of notifications to external systems | |
| 40 | * | |
| 41 | * @throws IllegalArgumentException if any argument is {@code null} | |
| 42 | */ | |
| 43 | @SuppressFBWarnings("EI_EXPOSE_REP2") | |
| 44 | public NotificationService(EventStore eventStore, PublishedMessageTrackerRepository messageTrackerRepository, | |
| 45 | NotificationPublisher notificationPublisher) { | |
| 46 |
1
1. <init> : removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED |
ErrorHandling.checkNull("Arguments must not be null: eventStore=%s, messageTrackerRepository=%s, notificationPublisher=%s", |
| 47 | eventStore, messageTrackerRepository, notificationPublisher); | |
| 48 | this.eventStore = eventStore; | |
| 49 | this.messageTrackerRepository = messageTrackerRepository; | |
| 50 | this.notificationPublisher = notificationPublisher; | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Gives a {@link NotificationLog} containing the most recent {@link Notification Notifications}. | |
| 55 | * | |
| 56 | * @param batchSize the maximum number of notifications in the notification log | |
| 57 | * | |
| 58 | * @return a notification log with the most recent notifications | |
| 59 | */ | |
| 60 | @Transactional(readOnly = true) | |
| 61 | public NotificationLog currentNotificationLog(int batchSize) { | |
| 62 | LOG.trace("currentNotificationLog: batchSize={}", batchSize); | |
| 63 | NotificationLogId notificationLogId = calculateCurrentNotificationLogId(batchSize); | |
| 64 | NotificationLog notificationLog = findNotificationLog(notificationLogId); | |
| 65 | LOG.trace("currentNotificationLog: {}", notificationLog); | |
| 66 |
1
1. currentNotificationLog : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::currentNotificationLog → KILLED |
return notificationLog; |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Gives an archived {@link NotificationLog}. | |
| 71 | * | |
| 72 | * @param notificationLogId represents the first and last {@link Notification} in the log | |
| 73 | * | |
| 74 | * @return an archived {@code NotificationLog} | |
| 75 | */ | |
| 76 | @Transactional(readOnly = true) | |
| 77 | @SuppressFBWarnings(value = "CRLF_INJECTION_LOGS", justification = "Logging only of objects, not user data") | |
| 78 | public NotificationLog notificationLog(NotificationLogId notificationLogId) { | |
| 79 | LOG.trace("notificationLog: notificationLogId={}", notificationLogId); | |
| 80 |
1
1. notificationLog : removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → SURVIVED |
ErrorHandling.checkNull("notificationLogId must not be null", notificationLogId); |
| 81 | NotificationLog notificationLog = findNotificationLog(notificationLogId); | |
| 82 | LOG.trace("notificationLog: {}", notificationLog); | |
| 83 |
1
1. notificationLog : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::notificationLog → KILLED |
return notificationLog; |
| 84 | } | |
| 85 | ||
| 86 | private NotificationLog findNotificationLog(NotificationLogId notificationLogId) { | |
| 87 | List<StoredEvent> storedEvents = eventStore.allEventsBetween(notificationLogId.low(), notificationLogId.high()); | |
| 88 | long lastStoredEventId = eventStore.lastStoredEventId(); | |
| 89 |
2
1. findNotificationLog : negated conditional → KILLED 2. findNotificationLog : changed conditional boundary → KILLED |
boolean archivedIndicator = notificationLogId.high() <= lastStoredEventId; |
| 90 |
2
1. findNotificationLog : changed conditional boundary → KILLED 2. findNotificationLog : negated conditional → KILLED |
NotificationLogId next = notificationLogId.high() < lastStoredEventId ? notificationLogId.next() : null; |
| 91 |
2
1. findNotificationLog : negated conditional → KILLED 2. findNotificationLog : changed conditional boundary → KILLED |
NotificationLogId previous = notificationLogId.low() > 1 ? notificationLogId.previous() : null; |
| 92 |
1
1. findNotificationLog : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::findNotificationLog → KILLED |
return new NotificationLog(notificationLogId, next, previous, notificationsFrom(storedEvents), archivedIndicator); |
| 93 | } | |
| 94 | ||
| 95 | private NotificationLogId calculateCurrentNotificationLogId(int batchSize) { | |
| 96 | long count = eventStore.lastStoredEventId(); | |
| 97 |
1
1. calculateCurrentNotificationLogId : Replaced long modulus with multiplication → KILLED |
long remainder = count % batchSize; |
| 98 |
1
1. calculateCurrentNotificationLogId : negated conditional → KILLED |
if (remainder == 0) { |
| 99 | remainder = batchSize; | |
| 100 | } | |
| 101 |
2
1. calculateCurrentNotificationLogId : Replaced long subtraction with addition → KILLED 2. calculateCurrentNotificationLogId : Replaced long addition with subtraction → KILLED |
long low = count - remainder + 1; |
| 102 |
2
1. calculateCurrentNotificationLogId : changed conditional boundary → SURVIVED 2. calculateCurrentNotificationLogId : negated conditional → KILLED |
if (low < 1) { |
| 103 | low = 1; | |
| 104 | } | |
| 105 |
2
1. calculateCurrentNotificationLogId : Replaced long addition with subtraction → KILLED 2. calculateCurrentNotificationLogId : Replaced long subtraction with addition → KILLED |
long high = low + batchSize - 1; |
| 106 |
1
1. calculateCurrentNotificationLogId : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::calculateCurrentNotificationLogId → KILLED |
return new NotificationLogId(low, high); |
| 107 | } | |
| 108 | ||
| 109 | private List<Notification> notificationsFrom(List<StoredEvent> storedEvents) { | |
| 110 | List<Notification> notifications = new ArrayList<>(); | |
| 111 | NotificationFactory notificationFactory = NotificationFactory.instance(eventStore); | |
| 112 | for (StoredEvent storedEvent : storedEvents) { | |
| 113 | notifications.add(notificationFactory.fromStoredEvent(storedEvent)); | |
| 114 | } | |
| 115 |
1
1. notificationsFrom : replaced return value with Collections.emptyList for com/reallifedeveloper/common/application/notification/NotificationService::notificationsFrom → KILLED |
return notifications; |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * Publishes notifications about all events that have occurred since the last publication to the given publication channel. | |
| 120 | * | |
| 121 | * @param publicationChannel the name of the publication channel to publish notifications on | |
| 122 | * | |
| 123 | * @throws IOException if publishing failed | |
| 124 | */ | |
| 125 | @Transactional | |
| 126 | public void publishNotifications(String publicationChannel) throws IOException { | |
| 127 | LOG.trace("publishNotifications: publicationChannel={}", removeCRLF(publicationChannel)); | |
| 128 | PublishedMessageTracker messageTracker = messageTracker(publicationChannel); | |
| 129 | List<Notification> notifications = unpublishedNotifications(messageTracker.lastPublishedMessageId()); | |
| 130 |
1
1. publishNotifications : removed call to com/reallifedeveloper/common/application/notification/NotificationPublisher::publish → KILLED |
notificationPublisher.publish(notifications, publicationChannel); |
| 131 |
1
1. publishNotifications : removed call to com/reallifedeveloper/common/application/notification/NotificationService::trackLastPublishedMessage → KILLED |
trackLastPublishedMessage(messageTracker, notifications); |
| 132 | LOG.trace("publishNotifications: done"); | |
| 133 | } | |
| 134 | ||
| 135 | private PublishedMessageTracker messageTracker(String publicationChannel) { | |
| 136 |
1
1. messageTracker : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::messageTracker → KILLED |
return messageTrackerRepository.findByPublicationChannel(publicationChannel) |
| 137 |
1
1. lambda$messageTracker$0 : replaced return value with null for com/reallifedeveloper/common/application/notification/NotificationService::lambda$messageTracker$0 → KILLED |
.orElseGet(() -> new PublishedMessageTracker(0, publicationChannel)); |
| 138 | } | |
| 139 | ||
| 140 | private List<Notification> unpublishedNotifications(long lastPublishedMessageId) { | |
| 141 | List<StoredEvent> storedEvents = eventStore.allEventsSince(lastPublishedMessageId); | |
| 142 |
1
1. unpublishedNotifications : replaced return value with Collections.emptyList for com/reallifedeveloper/common/application/notification/NotificationService::unpublishedNotifications → KILLED |
return notificationsFrom(storedEvents); |
| 143 | } | |
| 144 | ||
| 145 | private void trackLastPublishedMessage(PublishedMessageTracker messageTracker, List<Notification> notifications) { | |
| 146 |
1
1. trackLastPublishedMessage : negated conditional → KILLED |
if (!notifications.isEmpty()) { |
| 147 |
1
1. trackLastPublishedMessage : Replaced integer subtraction with addition → KILLED |
Notification lastNotification = notifications.get(notifications.size() - 1); |
| 148 |
1
1. trackLastPublishedMessage : removed call to com/reallifedeveloper/common/application/notification/PublishedMessageTracker::setLastPublishedMessageid → KILLED |
messageTracker.setLastPublishedMessageid(lastNotification.storedEventId()); |
| 149 | messageTrackerRepository.save(messageTracker); | |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Make finalize method final to avoid "Finalizer attacks" and corresponding SpotBugs warning (CT_CONSTRUCTOR_THROW). | |
| 155 | * | |
| 156 | * @see <a href="https://wiki.sei.cmu.edu/confluence/display/java/OBJ11-J.+Be+wary+of+letting+constructors+throw+exceptions"> | |
| 157 | * Explanation of finalizer attack</a> | |
| 158 | */ | |
| 159 | @Override | |
| 160 | @Deprecated | |
| 161 | @SuppressWarnings({ "Finalize", "checkstyle:NoFinalizer", "PMD.EmptyFinalizer" }) | |
| 162 | protected final void finalize() throws Throwable { | |
| 163 | // Do nothing | |
| 164 | } | |
| 165 | ||
| 166 | } | |
Mutations | ||
| 46 |
1.1 |
|
| 66 |
1.1 |
|
| 80 |
1.1 |
|
| 83 |
1.1 |
|
| 89 |
1.1 2.2 |
|
| 90 |
1.1 2.2 |
|
| 91 |
1.1 2.2 |
|
| 92 |
1.1 |
|
| 97 |
1.1 |
|
| 98 |
1.1 |
|
| 101 |
1.1 2.2 |
|
| 102 |
1.1 2.2 |
|
| 105 |
1.1 2.2 |
|
| 106 |
1.1 |
|
| 115 |
1.1 |
|
| 130 |
1.1 |
|
| 131 |
1.1 |
|
| 136 |
1.1 |
|
| 137 |
1.1 |
|
| 142 |
1.1 |
|
| 146 |
1.1 |
|
| 147 |
1.1 |
|
| 148 |
1.1 |