Skip to content

Commit a4b9647

Browse files
committed
Disabling firebase push notification route tokens in case of unregistered or expired tokens.
1 parent 5d78854 commit a4b9647

File tree

5 files changed

+119
-3
lines changed

5 files changed

+119
-3
lines changed

core/core-services-impl/src/main/java/com/sflpro/notifier/services/notification/impl/push/PushNotificationProcessorImpl.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import com.sflpro.notifier.db.entities.notification.push.PushNotification;
77
import com.sflpro.notifier.db.entities.notification.push.PushNotificationProviderType;
88
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipient;
9+
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipientStatus;
910
import com.sflpro.notifier.services.common.exception.ServicesRuntimeException;
1011
import com.sflpro.notifier.services.notification.exception.NotificationInvalidStateException;
1112
import com.sflpro.notifier.services.notification.push.PushNotificationProcessor;
13+
import com.sflpro.notifier.services.notification.push.PushNotificationRecipientService;
1214
import com.sflpro.notifier.services.notification.push.PushNotificationService;
15+
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
1316
import com.sflpro.notifier.spi.push.PlatformType;
1417
import com.sflpro.notifier.spi.push.PushMessage;
1518
import com.sflpro.notifier.spi.push.PushMessageSender;
@@ -55,6 +58,9 @@ public class PushNotificationProcessorImpl implements PushNotificationProcessor
5558
@Autowired
5659
private TemplateContentResolver templateContentResolver;
5760

61+
@Autowired
62+
private PushNotificationRecipientService pushNotificationRecipientService;
63+
5864
PushNotificationProcessorImpl() {
5965
logger.debug("Initializing push notification processing service");
6066
}
@@ -80,7 +86,16 @@ public void processNotification(@Nonnull final Long notificationId, @Nonnull fin
8086
}
8187
// Mark push notification as processed
8288
updatePushNotificationState(notificationId, NotificationState.SENT);
83-
} catch (final Exception ex) {
89+
}
90+
catch (final PushNotificationInvalidRouteTokenException ex) {
91+
logger.warn("Failed to process push notification with id - {}, because of invalid token of recipient with id - {}", notificationId, pushNotification.getRecipient().getId());
92+
updatePushNotificationState(notificationId, NotificationState.FAILED);
93+
pushNotificationRecipientService.updatePushNotificationRecipientStatus(
94+
pushNotification.getRecipient().getId(),
95+
PushNotificationRecipientStatus.DISABLED
96+
);
97+
}
98+
catch (final Exception ex) {
8499
final String message = "Error occurred while processing push notification with id - " + notificationId;
85100
updatePushNotificationState(notificationId, NotificationState.FAILED);
86101
throw new ServicesRuntimeException(message, ex);

core/core-services-impl/src/test/java/com/sflpro/notifier/services/notification/impl/push/PushNotificationProcessorImplTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import com.sflpro.notifier.db.entities.notification.NotificationState;
44
import com.sflpro.notifier.db.entities.notification.push.PushNotification;
55
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipient;
6+
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipientStatus;
67
import com.sflpro.notifier.services.notification.exception.NotificationInvalidStateException;
8+
import com.sflpro.notifier.services.notification.push.PushNotificationRecipientService;
79
import com.sflpro.notifier.services.notification.push.PushNotificationService;
810
import com.sflpro.notifier.services.test.AbstractServicesUnitTest;
11+
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
912
import com.sflpro.notifier.spi.push.PushMessage;
1013
import com.sflpro.notifier.spi.push.PushMessageSender;
1114
import com.sflpro.notifier.spi.push.PushMessageSendingResult;
@@ -41,6 +44,8 @@ public class PushNotificationProcessorImplTest extends AbstractServicesUnitTest
4144
@Mock
4245
private PushMessageSender pushMessageSender;
4346

47+
@Mock
48+
private PushNotificationRecipientService pushNotificationRecipientService;
4449

4550
/* Constructors */
4651
public PushNotificationProcessorImplTest() {
@@ -126,6 +131,37 @@ public void testProcessPushNotificationWhenExceptionOccursDuringProcessing() {
126131
verifyAll();
127132
}
128133

134+
public void testProcessPushNotificationWhenInvalidTokenExceptionOccursDuringProcessing() {
135+
// Test data
136+
final Long notificationId = 1L;
137+
final PushNotification notification = getServicesImplTestHelper().createPushNotification();
138+
notification.setId(notificationId);
139+
notification.setState(NotificationState.CREATED);
140+
final Long recipientId = 2L;
141+
final PushNotificationRecipient recipient = getServicesImplTestHelper().createPushNotificationSnsRecipient();
142+
recipient.setId(recipientId);
143+
notification.setRecipient(recipient);
144+
final PushNotificationInvalidRouteTokenException exceptionDuringSending = new PushNotificationInvalidRouteTokenException(
145+
UUID.randomUUID().toString(), "Exception for testing error flow", null
146+
);
147+
// Reset
148+
resetAll();
149+
// Expectations
150+
expect(pushNotificationService.getPushNotificationForProcessing(eq(notificationId))).andReturn(notification).once();
151+
expect(pushNotificationService.updateNotificationState(notificationId, NotificationState.PROCESSING)).andReturn(notification).once();
152+
expect(pushMessageServiceProvider.lookupPushMessageSender(notification.getRecipient().getType()))
153+
.andReturn(Optional.of(pushMessageSender));
154+
expect(pushMessageSender.send(isA(PushMessage.class))).andThrow(exceptionDuringSending);
155+
expect(pushNotificationService.updateNotificationState(notificationId, NotificationState.FAILED)).andReturn(notification).once();
156+
expect(pushNotificationRecipientService.updatePushNotificationRecipientStatus(notification.getRecipient().getId(), PushNotificationRecipientStatus.DISABLED));
157+
// Replay
158+
replayAll();
159+
// Run test scenario
160+
pushNotificationProcessingService.processNotification(notificationId, Collections.emptyMap());
161+
// Verify
162+
verifyAll();
163+
}
164+
129165
@Test
130166
public void testProcessPushNotification() {
131167
// Test data

infra/infra-integrations/infra-integrations-push/src/main/java/com/sflpro/notifier/externalclients/push/firebase/FirebasePushMessageSender.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sflpro.notifier.externalclients.push.firebase;
22

33
import com.google.firebase.messaging.*;
4+
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
45
import com.sflpro.notifier.spi.push.PlatformType;
56
import com.sflpro.notifier.spi.push.PushMessage;
67
import com.sflpro.notifier.spi.push.PushMessageSender;
@@ -27,6 +28,8 @@ class FirebasePushMessageSender implements PushMessageSender {
2728

2829
private static final Logger logger = LoggerFactory.getLogger(FirebasePushMessageSender.class);
2930

31+
private static final String UNREGISTERED_ERROR_CODE = "UNREGISTERED";
32+
3033
private static final String TITLE = "title";
3134

3235
private static final String BODY = "body";
@@ -62,6 +65,15 @@ public PushMessageSendingResult send(final PushMessage message) {
6265
platformConfigurationHandler(message.platformType()).ifPresent(handler -> handler.accept(message, builder));
6366
return PushMessageSendingResult.of(firebaseMessaging.send(builder.build()));
6467
} catch (final FirebaseMessagingException ex) {
68+
final String errorCode = ex.getErrorCode();
69+
if(UNREGISTERED_ERROR_CODE.equals(errorCode)) {
70+
logger.debug("Unable to send message with subject {}, firebase route token is not registered", message.subject());
71+
throw new PushNotificationInvalidRouteTokenException(
72+
message.destinationRouteToken(),
73+
"Firebase notification sender failed to send message with subject " + message.subject() + " with error" + errorCode,
74+
ex
75+
);
76+
}
6577
logger.error("Unable to send message with subject {}.", message.subject());
6678
throw new MessageSendingFaildException("Filed to send message using firebase cloud messaging.", ex);
6779
}

infra/infra-integrations/infra-integrations-push/src/test/java/com/sflpro/notifier/externalclients/push/firebase/FirebasePushMessageSenderTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
import com.google.firebase.messaging.FirebaseMessagingException;
55
import com.google.firebase.messaging.Message;
66
import com.sflpro.notifier.externalclients.push.test.AbstractPushNotificationUnitTest;
7+
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
78
import com.sflpro.notifier.spi.push.PlatformType;
89
import com.sflpro.notifier.spi.push.PushMessage;
910
import com.sflpro.notifier.spi.push.PushMessageSender;
1011
import org.junit.Before;
1112
import org.junit.Test;
1213
import org.mockito.Mock;
14+
import org.mockito.Mockito;
1315

1416
import java.util.HashMap;
1517
import java.util.Map;
1618
import java.util.Properties;
1719

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.assertj.core.api.Assertions.*;
2021
import static org.mockito.ArgumentMatchers.isA;
2122
import static org.mockito.Mockito.*;
2223

@@ -177,6 +178,43 @@ public void testSendToIOSDeviceWithProvidedConfigs() throws FirebaseMessagingExc
177178
verifyZeroInteractions(defaultAndroidConfig, defaultApnsConfig);
178179
}
179180

181+
@Test
182+
public void testSendWithUnregisteredTokenThrowsException() throws FirebaseMessagingException {
183+
final Map<String, String> properties = new HashMap<>();
184+
properties.put(uuid(), uuid());
185+
final FirebaseMessagingException firebaseException = mock(FirebaseMessagingException.class);
186+
final PushMessage message = PushMessage.of(
187+
uuid(),
188+
uuid(),
189+
uuid(),
190+
PlatformType.GCM,
191+
properties
192+
);
193+
properties.put("title", message.subject());
194+
properties.put("body", message.body());
195+
final String messageId = uuid();
196+
final String ttlKey = "ttl";
197+
final String priorityKey = "priority";
198+
final String collapseKey = "collapseKey";
199+
final String restrictedPackageNameKey = "restrictedPackageName";
200+
when(defaultAndroidConfig.getProperty(ttlKey)).thenReturn("10");
201+
when(defaultAndroidConfig.getProperty(priorityKey)).thenReturn("HIGH");
202+
when(defaultAndroidConfig.getProperty(collapseKey)).thenReturn(uuid());
203+
when(defaultAndroidConfig.getProperty(restrictedPackageNameKey)).thenReturn(uuid());
204+
when(firebaseMessaging.send(isA(Message.class))).thenThrow(firebaseException);
205+
when(firebaseException.getErrorCode()).thenReturn("UNREGISTERED");
206+
assertThatThrownBy(() -> pushMessageSender.send(message))
207+
.isInstanceOf(PushNotificationInvalidRouteTokenException.class)
208+
.hasFieldOrPropertyWithValue("routeToken", message.destinationRouteToken())
209+
.hasFieldOrPropertyWithValue("cause", firebaseException);
210+
verify(firebaseMessaging).send(isA(Message.class));
211+
verify(defaultAndroidConfig).getProperty(ttlKey);
212+
verify(defaultAndroidConfig).getProperty(priorityKey);
213+
verify(defaultAndroidConfig).getProperty(collapseKey);
214+
verify(defaultAndroidConfig).getProperty(restrictedPackageNameKey);
215+
verifyZeroInteractions(defaultApnsConfig);
216+
}
217+
180218
private static void checkProperties(final PushMessage pushMessage, final Message message) {
181219
assertThat(message)
182220
.hasFieldOrPropertyWithValue("data", pushMessage.properties())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.sflpro.notifier.spi.exception;
2+
3+
public class PushNotificationInvalidRouteTokenException extends RuntimeException {
4+
5+
private final String routeToken;
6+
7+
public PushNotificationInvalidRouteTokenException(final String routeToken, final String message, final Throwable cause) {
8+
super(message, cause);
9+
this.routeToken = routeToken;
10+
}
11+
12+
public String getRouteToken() {
13+
return routeToken;
14+
}
15+
}

0 commit comments

Comments
 (0)