package com.devicehive.eventbus;
/*
* #%L
* DeviceHive Backend Logic
* %%
* Copyright (C) 2016 DataArt
* %%
* 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.
* #L%
*/
import com.devicehive.model.eventbus.Subscriber;
import com.devicehive.model.eventbus.Subscription;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Class for handling all subscribe, unsubscribe and get subscribers tricky logic
*/
class SubscriberRegistry {
/**
* Map for holding subscriptions for particular subscription request id (i.e. subscriber).
* The KEY in this map is an id of subscriber (subscription request) and the VALUE is a set of subscriptions for this subscriber.
*
* This map keeps track of all subscriptions for single subscriber so that it is possible to remove all of them
* from {@link SubscriberRegistry#subscriptions} map during {@link SubscriberRegistry#unregister(Subscriber)} call
*/
private final ConcurrentHashMap<String, CopyOnWriteArraySet<Subscription>> subscriberSubscriptions =
new ConcurrentHashMap<>();
/**
* Map that contains an information about subscribers for single subscription.
* The KEY is subscription (e.g. subscription on device notifications) and the VALUE is a set of all subscribers.
* This map is used for actual routing of messages through the event bus
*/
private final ConcurrentHashMap<Subscription, CopyOnWriteArraySet<Subscriber>> subscriptions =
new ConcurrentHashMap<>();
/**
* Registers subscription and subscriber in registry maps.
* Performs following steps:
* - if subscriber doesn't have any subscriptions in {@link SubscriberRegistry#subscriberSubscriptions} - creates an empty list for him;
* - adds subscription into subscriber's list in {@link SubscriberRegistry#subscriberSubscriptions};
* - if nobody is subscribed to this subscription in {@link SubscriberRegistry#subscriptions} - initializes the list;
* - adds subscriber to this subscription's list in {@link SubscriberRegistry#subscriptions}
*
* @param subscriber - subscriber
* @param subscription - subscription to subscribe to
*/
void register(Subscriber subscriber, Subscription subscription) {
CopyOnWriteArraySet<Subscription> subscriptions = subscriberSubscriptions.get(subscriber.getId());
if (subscriptions == null) {
//initialize list in a thread safe manner
CopyOnWriteArraySet<Subscription> newSet = new CopyOnWriteArraySet<>();
subscriptions = firstNonNull(subscriberSubscriptions.putIfAbsent(subscriber.getId(), newSet), newSet);
}
subscriptions.add(subscription);
CopyOnWriteArraySet<Subscriber> subscribers = this.subscriptions.get(subscription);
if (subscribers == null) {
//initialize list in a thread safe manner
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
subscribers = firstNonNull(this.subscriptions.putIfAbsent(subscription, newSet), newSet);
}
subscribers.add(subscriber);
}
/**
* Unregisters subscriber from registry maps:
* - gets all subscriber's subscriptions from {@link SubscriberRegistry#subscriberSubscriptions}
* - removes subscriber from each subscription's list in {@link SubscriberRegistry#subscriptions}
* - removes entry from {@link SubscriberRegistry#subscriberSubscriptions}
*
* @param subscriber - subscriber
*/
void unregister(Subscriber subscriber) {
CopyOnWriteArraySet<Subscription> subs =
subscriberSubscriptions.getOrDefault(subscriber.getId(), new CopyOnWriteArraySet<>());
subs.forEach(s -> {
CopyOnWriteArraySet<Subscriber> subscribers = this.subscriptions.get(s);
if (subscribers != null) {
subscribers.remove(subscriber);
}
});
}
/**
* @param subscription - subscription
* @return - list of subscribers for subscription
*/
Collection<Subscriber> getSubscribers(Subscription subscription) {
Assert.notNull(subscription);
return this.subscriptions.getOrDefault(subscription, new CopyOnWriteArraySet<>());
}
Collection<Subscription> getSubscriptions(Subscriber subscriber) {
Assert.notNull(subscriber);
return this.subscriberSubscriptions.getOrDefault(subscriber.getId(), new CopyOnWriteArraySet<>());
}
private static <T> T firstNonNull(T first, T second) {
Assert.notNull(second);
return first != null ? first : second;
}
}