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({ "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 |