com.ocadotechnology.notification.WithinAppNotificationRouter Maven / Gradle / Ivy
/*
* Copyright © 2017-2023 Ocado (Ocava)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ocadotechnology.notification;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.ocadotechnology.event.scheduling.EventScheduler;
import com.ocadotechnology.event.scheduling.EventSchedulerType;
import com.ocadotechnology.utils.RememberingSupplier;
import com.ocadotechnology.validation.Failer;
class WithinAppNotificationRouter implements NotificationRouter {
private static final Logger logger = LoggerFactory.getLogger(WithinAppNotificationRouter.class);
/**
* Configuration to allow scheduling the cross-thread broadcasts first.
*/
private static final String CROSS_THREAD_BROADCAST_TYPE = "CROSS_THREAD_FIRST";
private static final String CONFIGURED_BROADCAST_TYPE = System.getProperties().getProperty("com.ocadotechnology.notificationrouter.broadcast");
private static boolean scheduleCrossThreadBroadcastFirst = CROSS_THREAD_BROADCAST_TYPE.equalsIgnoreCase(CONFIGURED_BROADCAST_TYPE);
private static class SingletonHolder {
private static final WithinAppNotificationRouter instance = new WithinAppNotificationRouter();
}
static WithinAppNotificationRouter get() {
return SingletonHolder.instance;
}
/** Singleton */
private WithinAppNotificationRouter(){}
private final AtomicReference>> broadcasters = new AtomicReference<>(ImmutableList.of());
@Override
public void broadcast(T notification) {
RememberingSupplier notificationHolder = new RememberingSupplier<>(notification);
broadcastImplementation(notificationHolder, notification.getClass(), Broadcaster::canHandleNotification);
}
@Override
public void broadcast(Supplier concreteMessageNotificationSupplier, Class notificationClass) {
RememberingSupplier rememberingSupplier = new RememberingSupplier<>(concreteMessageNotificationSupplier);
broadcastImplementation(rememberingSupplier, notificationClass, Broadcaster::isNotificationRegistered);
}
void broadcastImplementation(
RememberingSupplier messageSupplier,
Class> notificationClass,
BiPredicate, Class>> shouldHandToBroadcaster) {
if (logger.isTraceEnabled()) {
logger.trace("Broadcasting {}", messageSupplier.get());
}
if (scheduleCrossThreadBroadcastFirst) {
passToBroadcastersCrossThreadFirst(messageSupplier, notificationClass, shouldHandToBroadcaster);
return;
}
passToBroadcastersInOrder(messageSupplier, notificationClass, shouldHandToBroadcaster);
}
/**
* @deprecated (8.15)
* Provides no guarantees that cross-thread notifications will be received in the order they are sent.
* Use {@link #passToBroadcastersCrossThreadFirst} passToBroadcastersCrossThreadFirst instead.
*/
@Deprecated
private void passToBroadcastersInOrder(RememberingSupplier messageSupplier, Class> notificationClass, BiPredicate, Class>> shouldHandToBroadcaster) {
for (Broadcaster> broadcaster : broadcasters.get()) {
if (shouldHandToBroadcaster.test(broadcaster, notificationClass)) {
broadcaster.broadcast(messageSupplier.get());
}
}
}
/**
* Guarantees that cross-thread notifications are received in the order they are sent.
*/
private void passToBroadcastersCrossThreadFirst(RememberingSupplier messageSupplier, Class> notificationClass, BiPredicate, Class>> shouldHandToBroadcaster) {
Broadcaster> inThreadBroadcaster = null;
for (Broadcaster> broadcaster : broadcasters.get()) {
if (!shouldHandToBroadcaster.test(broadcaster, notificationClass)) {
continue;
}
if (broadcaster.requiresScheduling()) {
broadcaster.scheduleBroadcast(messageSupplier.get());
} else {
Preconditions.checkState(inThreadBroadcaster == null, "There should be at most one broadcaster per scheduler/thread.");
inThreadBroadcaster = broadcaster;
}
}
if (inThreadBroadcaster != null) {
inThreadBroadcaster.directBroadcast(messageSupplier.get());
}
}
@Override
public void addHandler(Subscriber handler) {
EventSchedulerType schedulerType = handler.getSchedulerType();
Broadcaster> broadcaster = broadcasters.get().stream()
.filter(b -> b.handlesSubscriber(schedulerType))
.findFirst()
.orElseThrow(() -> Failer.fail("Attempting to register subscriber of scheduler type %s but there are no registered broadcasters for this type.", schedulerType));
// The callee needs to be threadsafe -- no reason 2 threads can't get here at the same time
broadcaster.addHandler(handler);
}
@Override
public void registerExecutionLayer(EventScheduler scheduler, NotificationBus notificationBus) {
registerExecutionLayer(new Broadcaster<>(scheduler, notificationBus));
}
/**
* Insertion order is used to send notification (LOGGING should be first)
*/
@Override
public void registerExecutionLayer(Broadcaster newBroadcaster) {
broadcasters.updateAndGet(broadcasters -> registerExecutionLayer(broadcasters, newBroadcaster));
}
// Can be retried multiple times -- must be side-effect free.
private static ImmutableList> registerExecutionLayer(ImmutableList> broadcasters, Broadcaster newBroadcaster) {
if (broadcasters.isEmpty()) {
String broadcasterOrder = scheduleCrossThreadBroadcastFirst ? "CROSS_THREAD_FIRST" : "BROADCASTER_REGISTRATION_ORDER";
logger.info("The configured broadcast order is: {}", broadcasterOrder);
}
Preconditions.checkArgument(!alreadyHandlesType(broadcasters, newBroadcaster.getSchedulerType()), "A broadcaster with type %s has already been registered.", newBroadcaster.getSchedulerType());
return copyAndAdd(broadcasters, newBroadcaster);
}
private static ImmutableList> copyAndAdd(ImmutableList> broadcasters, Broadcaster newBroadcaster) {
return ImmutableList.>builder().addAll(broadcasters).add(newBroadcaster).build();
}
private static boolean alreadyHandlesType(ImmutableList> broadcasters, EventSchedulerType broadcasterType) {
return broadcasters.stream().anyMatch(b -> b.handlesSubscriber(broadcasterType));
}
@Override
public void clearAllHandlers() {
broadcasters.getAndSet(ImmutableList.of()).forEach(Broadcaster::clearAllHandlers);
}
@VisibleForTesting
static void setScheduleCrossThreadBroadcastFirst(boolean scheduleCrossThreadBroadcastFirst) {
WithinAppNotificationRouter.scheduleCrossThreadBroadcastFirst = scheduleCrossThreadBroadcastFirst;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy