/*
* Copyright (c) 2012 Mike Heath. All rights reserved.
*
* 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 cloudeventbus.hub;
import cloudeventbus.Subject;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Mike Heath <elcapo@gmail.com>
*/
public abstract class AbstractHub<T> implements SubscribeableHub<T> {
private final WildCardNode wildcardSubscriptions = new WildCardNode();
private final ConcurrentMap<Subject, Collection<Handler<T>>> subscriptions = new ConcurrentHashMap<>();
@Override
public SubscriptionHandle subscribe(Subject subject, final Handler<T> handler) {
if (subject.isWildCard()) {
WildCardNode currentNode = wildcardSubscriptions;
for (String part : splitSubject(subject)) {
if (Subject.WILD_CARD_TOKEN.equals(part)) {
break;
}
currentNode = currentNode.getChild(part, true);
}
return addHandler(handler, currentNode.getHandlers());
}
return addHandler(handler, getHandlers(subject));
}
private String[] splitSubject(Subject subject) {
return subject.toString().split("\\.");
}
private SubscriptionHandle addHandler(final Handler<T> handler, final Collection<Handler<T>> handlers) {
handlers.add(handler);
return new SubscriptionHandle() {
@Override
public void remove() {
handlers.remove(handler);
}
};
}
private Collection<Handler<T>> getHandlers(Subject subject) {
final Collection<Handler<T>> handlers = subscriptions.get(subject);
if (handlers == null) {
final Collection<Handler<T>> newHandlers = new CopyOnWriteArrayList<>();
final Collection<Handler<T>> existingHandlers = subscriptions.putIfAbsent(subject, newHandlers);
// If another thread added a handler collection, use it and discard the collection just created
if (existingHandlers != null) {
return existingHandlers;
}
return newHandlers;
}
return handlers;
}
@Override
public void publish(Subject subject, Subject replySubject, String body) {
if (subject.isWildCard()) {
throw new IllegalArgumentException("Unable to publish to a wildcard subscription.");
}
final Set<Handler<T>> handlers = new HashSet<>();
// Add wildcard handlers
WildCardNode currentNode = wildcardSubscriptions;
for (String part : splitSubject(subject)) {
handlers.addAll(currentNode.getHandlers());
currentNode = currentNode.getChild(part, false);
if (currentNode == null) {
break;
}
}
// Add static handlers
final Collection<Handler<T>> nonWildCardSubscriptions = subscriptions.get(subject);
if (nonWildCardSubscriptions != null) {
handlers.addAll(nonWildCardSubscriptions);
}
// If we have any handlers, encode and propagate the message.
if (handlers.size() > 0) {
final T message = encode(subject, replySubject, body, handlers.size());
for (Handler<T> handler : handlers) {
handler.publish(message);
}
}
}
protected abstract T encode(Subject subject, Subject replySubject, String body, int recipientCount);
private class WildCardNode {
private final Collection<Handler<T>> handlers = new CopyOnWriteArrayList<>();
private final ConcurrentMap<String, WildCardNode> children = new ConcurrentHashMap<>();
public Collection<Handler<T>> getHandlers() {
return handlers;
}
public WildCardNode getChild(String nodeName, boolean createIfMissing) {
final WildCardNode node = children.get(nodeName);
if (node == null && createIfMissing) {
final WildCardNode newNode = new WildCardNode();
final WildCardNode existingNode = children.putIfAbsent(nodeName, newNode);
if (existingNode != null) {
return existingNode;
}
return newNode;
}
return node;
}
}
}