/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. 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 com.noctarius.tengi.spi.connection; import com.noctarius.tengi.core.connection.Connection; import com.noctarius.tengi.core.connection.Transport; import com.noctarius.tengi.core.listener.ClosedListener; import com.noctarius.tengi.core.listener.DisconnectedListener; import com.noctarius.tengi.core.listener.ExceptionListener; import com.noctarius.tengi.core.listener.Listener; import com.noctarius.tengi.core.listener.MessageListener; import com.noctarius.tengi.core.model.Identifier; import com.noctarius.tengi.core.model.Message; import com.noctarius.tengi.spi.buffer.MemoryBuffer; import com.noctarius.tengi.spi.serialization.Serializer; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Stream; /** * The <tt>AbstractConnection</tt> acts as a base class for server- and client-side * {@link com.noctarius.tengi.core.connection.Connection} implementations. It handles * listener registrations as well as basic notification logic. */ public abstract class AbstractConnection implements Connection { private final ConnectionContext connectionContext; private final Identifier connectionId; private final Transport transport; private final Serializer serializer; private final Map<Identifier, MessageListener> messageListeners = new ConcurrentHashMap<>(); private final Map<Identifier, Listener> listeners = new ConcurrentHashMap<>(); /** * Constructs a new <tt>AbstractConnection</tt> using the given parameters. * * @param connectionContext the <tt>ConnectionContext</tt> to bind * @param connectionId the connection's connectionId * @param transport the <tt>Transport</tt> that received the connection request * @param serializer the <tt>Serializer</tt> to bind */ protected AbstractConnection(ConnectionContext connectionContext, Identifier connectionId, // Transport transport, Serializer serializer) { this.connectionContext = connectionContext; this.connectionId = connectionId; this.transport = transport; this.serializer = serializer; } @Override public Identifier getConnectionId() { return connectionId; } @Override public Transport getTransport() { return transport; } @Override public Identifier addMessageListener(MessageListener messageListener) { for (MessageListener ml : messageListeners.values()) { if (ml == messageListener) { throw new IllegalStateException("MessageListener is already registered"); } } Identifier identifier = Identifier.randomIdentifier(); messageListeners.put(identifier, messageListener); return identifier; } @Override public void removeMessageListener(Identifier registrationIdentifier) { messageListeners.remove(registrationIdentifier); } @Override public Identifier addConnectionListener(Listener listener) { for (Listener cl : listeners.values()) { if (cl == listener) { throw new IllegalStateException("ConnectionListener is already registered"); } } Identifier identifier = Identifier.randomIdentifier(); listeners.put(identifier, listener); return identifier; } @Override public void removeConnectionListener(Identifier registrationIdentifier) { listeners.remove(registrationIdentifier); } @Override public <O> CompletableFuture<Message> writeObject(O object) throws Exception { Message message; if (object instanceof Message) { message = (Message) object; } else { message = Message.create(object); } MemoryBuffer memoryBuffer = serializer.writeObject("message", message); return connectionContext.writeMemoryBuffer(memoryBuffer, message); } @Override public CompletableFuture<Connection> disconnect() { return connectionContext.close(this); } @Override public void close() throws Exception { disconnect().get(); } /** * Notifies all registered {@link com.noctarius.tengi.core.listener.Listener}s about an * unexpected exception occurrence. * * @param throwable the <tt>Throwable</tt> instance to delegate to listeners */ public void notifyException(Throwable throwable) { notify(ExceptionListener.class, l -> l.onExceptionally(this, throwable)); } /** * Notifies all registered {@link com.noctarius.tengi.core.listener.Listener}s about * the connection's disconnect event. */ public void notifyDisconnect() { notify(DisconnectedListener.class, l -> l.onDisconnect(this)); } /** * Notifies all registered {@link com.noctarius.tengi.core.listener.Listener}s about * the connection's close event. */ public void notifyClose() { notify(ClosedListener.class, l -> l.onClose(this)); } /** * Returns all registered {@link com.noctarius.tengi.core.listener.MessageListener}s. The returned * collection is not modifiable. * * @return all registered <tt>MessageListener</tt>s */ protected Collection<MessageListener> getMessageListeners() { return Collections.unmodifiableCollection(messageListeners.values()); } /** * Returns all registered {@link com.noctarius.tengi.core.listener.ConnectionListener}s. The returned * collection is not modifiable. * * @return all registered <tt>ConnectionListener</tt>s */ protected Collection<Listener> getConnectionListeners() { return Collections.unmodifiableCollection(listeners.values()); } /** * Returns the <tt>ConnectionContext</tt> bound to this connection. * * @return the bound <tt>ConnectionContext</tt> */ protected ConnectionContext getConnectionContext() { return connectionContext; } private <L extends Listener> void notify(Class<L> clazz, Consumer<L> notifier) { Stream<Listener> listeners = getConnectionListeners().stream(); listeners.filter(l -> clazz.isInstance(l)).map(l -> clazz.cast(l)).forEach(notifier); } }