org.smallmind.bayeux.oumuamua.server.impl.OumuamuaChannel Maven / Gradle / Ivy
/*
* Copyright (c) 2007 through 2024 David Berkman
*
* This file is part of the SmallMind Code Project.
*
* The SmallMind Code Project is free software, you can redistribute
* it and/or modify it under either, at your discretion...
*
* 1) The terms of GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* ...or...
*
* 2) The terms of the Apache License, Version 2.0.
*
* The SmallMind Code Project is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License or Apache License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and the Apache License along with the SmallMind Code Project. If not, see
* or .
*
* Additional permission under the GNU Affero GPL version 3 section 7
* ------------------------------------------------------------------
* If you modify this Program, or any covered work, by linking or
* combining it with other code, such other code is not for that reason
* alone subject to any of the requirements of the GNU Affero GPL
* version 3.
*/
package org.smallmind.bayeux.oumuamua.server.impl;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import org.smallmind.bayeux.oumuamua.server.api.Channel;
import org.smallmind.bayeux.oumuamua.server.api.Packet;
import org.smallmind.bayeux.oumuamua.server.api.PacketType;
import org.smallmind.bayeux.oumuamua.server.api.Route;
import org.smallmind.bayeux.oumuamua.server.api.Session;
import org.smallmind.bayeux.oumuamua.server.api.json.Message;
import org.smallmind.bayeux.oumuamua.server.api.json.ObjectValue;
import org.smallmind.bayeux.oumuamua.server.api.json.Value;
import org.smallmind.bayeux.oumuamua.server.spi.AbstractAttributed;
import org.smallmind.bayeux.oumuamua.server.spi.DefaultRoute;
import org.smallmind.bayeux.oumuamua.server.spi.json.PacketUtility;
public class OumuamuaChannel> extends AbstractAttributed implements Channel {
private final DefaultRoute route;
private final ChannelRoot root;
private final ConcurrentHashMap> sessionMap = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue> listenerList = new ConcurrentLinkedQueue<>();
private final AtomicBoolean reflecting = new AtomicBoolean();
private final AtomicBoolean streaming = new AtomicBoolean();
private final BiConsumer, Session> onSubscribedCallback;
private final BiConsumer, Session> onUnsubscribedCallback;
private final long timeToLiveMilliseconds;
private boolean persistent;
private boolean terminal;
private long quiescentTimestamp;
private int persistentListenerCount;
public OumuamuaChannel (BiConsumer, Session> onSubscribedCallback, BiConsumer, Session> onUnsubscribedCallback, long timeToLiveMilliseconds, DefaultRoute route, ChannelRoot root) {
this.onSubscribedCallback = onSubscribedCallback;
this.onUnsubscribedCallback = onUnsubscribedCallback;
this.timeToLiveMilliseconds = timeToLiveMilliseconds;
this.route = route;
this.root = root;
reflecting.set(root.isReflecting(route));
streaming.set(root.isStreaming(route));
quiescentTimestamp = System.currentTimeMillis();
}
private void onSubscribed (Session session) {
onSubscribedCallback.accept(this, session);
for (Listener listener : listenerList) {
if (SessionListener.class.isAssignableFrom(listener.getClass())) {
((SessionListener)listener).onSubscribed(session);
}
}
}
private void onUnsubscribed (Session session) {
onUnsubscribedCallback.accept(this, session);
for (Listener listener : listenerList) {
if (SessionListener.class.isAssignableFrom(listener.getClass())) {
((SessionListener)listener).onUnsubscribed(session);
}
}
}
private Packet onProcessing (Session sender, Packet packet) {
if (PacketType.DELIVERY.equals(packet.getPacketType())) {
for (Listener listener : listenerList) {
if (PacketListener.class.isAssignableFrom(listener.getClass())) {
if ((packet = ((PacketListener)listener).onDelivery(sender, packet)) == null) {
break;
}
}
}
}
return packet;
}
@Override
public synchronized void addListener (Listener listener) {
if (!terminal) {
if (listenerList.add(listener) && listener.isPersistent()) {
persistentListenerCount++;
quiescentTimestamp = 0;
}
}
}
@Override
public synchronized void removeListener (Listener listener) {
if (listenerList.remove(listener) && listener.isPersistent()) {
if ((--persistentListenerCount <= 0) && sessionMap.isEmpty()) {
quiescentTimestamp = System.currentTimeMillis();
}
}
}
@Override
public Route getRoute () {
return route;
}
@Override
public synchronized boolean isPersistent () {
return persistent;
}
@Override
public synchronized void setPersistent (boolean persistent) {
this.persistent = persistent;
}
@Override
public boolean isReflecting () {
return reflecting.get();
}
@Override
public void setReflecting (boolean reflecting) {
this.reflecting.set(reflecting);
}
@Override
public boolean isStreaming () {
return streaming.get();
}
@Override
public void setStreaming (boolean streaming) {
this.streaming.set(streaming);
}
@Override
public synchronized boolean subscribe (Session session) {
if (terminal) {
return false;
}
if (sessionMap.putIfAbsent(session.getId(), session) == null) {
onSubscribed(session);
}
quiescentTimestamp = 0;
return true;
}
@Override
public synchronized void unsubscribe (Session session) {
Session unsubscribedSession;
if ((unsubscribedSession = sessionMap.remove(session.getId())) != null) {
onUnsubscribed(unsubscribedSession);
if (sessionMap.isEmpty() && (persistentListenerCount <= 0)) {
quiescentTimestamp = System.currentTimeMillis();
}
}
}
@Override
public synchronized boolean isRemovable (long now) {
return (!persistent) && (quiescentTimestamp > 0) && ((now - quiescentTimestamp) >= timeToLiveMilliseconds);
}
public synchronized OumuamuaChannel terminate () {
HashSet> unsubscribedSet = new HashSet<>(sessionMap.values());
terminal = true;
sessionMap.clear();
for (Session unsubscribedSession : unsubscribedSet) {
onUnsubscribed(unsubscribedSession);
}
if (persistentListenerCount <= 0) {
quiescentTimestamp = System.currentTimeMillis();
}
return this;
}
@Override
public void deliver (Session sender, Packet packet, Set sessionIdSet) {
Packet processedPacket;
// Changes by channel listeners here will be seen only by sessions in this delivery stream
if ((processedPacket = onProcessing(sender, PacketUtility.freezePacket(packet))) != null) {
for (Session session : sessionMap.values()) {
if (sessionIdSet.add(session.getId()) && ((processedPacket.getSenderId() == null) || (!session.getId().equals(processedPacket.getSenderId())) || reflecting.get())) {
// Changes made by session listeners further down the line will be seen only by the hosting session
session.deliver(this, sender, PacketUtility.freezePacket(processedPacket));
}
}
}
}
@Override
public void publish (ObjectValue data) {
root.forward(this, new Packet<>(PacketType.DELIVERY, null, getRoute(), (Message)root.getCodec().create().put(Message.CHANNEL, getRoute().getPath()).put(Message.DATA, data)));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy