/* Copyright (c) 2011 Danish Maritime Authority.
*
* 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 net.maritimecloud.internal.mms.client.connection;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import net.maritimecloud.internal.mms.client.ClientInfo;
import net.maritimecloud.internal.mms.client.connection.session.Session;
import net.maritimecloud.internal.mms.client.connection.session.SessionListener;
import net.maritimecloud.internal.mms.client.connection.transport.ClientTransportFactory;
import net.maritimecloud.internal.mms.messages.spi.MmsMessage;
import net.maritimecloud.internal.util.concurrent.CompletableFuture;
import net.maritimecloud.internal.util.logging.Logger;
import net.maritimecloud.message.Message;
import net.maritimecloud.net.mms.MmsClientConfiguration;
import net.maritimecloud.net.mms.MmsConnection;
import net.maritimecloud.net.mms.MmsConnectionClosingCode;
import org.cakeframework.container.lifecycle.RunOnStop;
/**
*
* @author Kasper Nielsen
*/
public class ClientConnection {
/** The logger. */
private static final Logger LOGGER = Logger.get(ClientConnection.class);
private final MmsConnection.Listener connectionListener;
final ClientInfo clientInfo;
/** The manager responsible for creating new WebSockets. */
private final ClientTransportFactory ctm;
/** Whether or not the connection is enabled. */
private volatile boolean isEnabled;
private final ReentrantLock lock = new ReentrantLock();
volatile Session session;
final SessionListener sessionListener = new SessionListener() {
@Override
public void onMessage(MmsMessage message) {
listenerOnMessage(message);
}
@Override
public void onSessionClose(MmsConnectionClosingCode closingCode) {
listenerConnectionClosed(closingCode);
}
};
/** Signaled when the state of the connection manager changes. */
final Condition stateChange = lock.newCondition();
/** Consumers of messages. */
private final CopyOnWriteArraySet<Consumer<MmsMessage>> subscribers = new CopyOnWriteArraySet<>();
public ClientConnection(ClientTransportFactory ctm, ClientInfo info, MmsClientConfiguration b) {
this.ctm = requireNonNull(ctm);
this.clientInfo = requireNonNull(info);
this.connectionListener = new MmsConnectionListenerInvoker(this, b);
}
public boolean await(boolean connected, long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
for (;;) {
if (isConnected() == connected) {
return true;
} else if (nanos <= 0) {
return false;
}
nanos = stateChange.awaitNanos(nanos);
}
} finally {
lock.unlock();
}
}
public boolean isConnected() {
Session session = this.session;
return session != null && session.isConnected();
}
public boolean isEnabled() {
return isEnabled;
}
void listenerConnectionClosed(MmsConnectionClosingCode closingCode) {
lock.lock();
try {
if (isEnabled) { // Reconnect
session = Session.createNewSessionAndConnect(ctm, clientInfo, sessionListener, connectionListener);
} else {
session = null;
}
stateChange.signalAll();
} finally {
lock.unlock();
}
}
void listenerOnMessage(MmsMessage message) {
// save somewhere as delivered
for (Consumer<MmsMessage> c : subscribers) {
try {
c.accept(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public CompletableFuture<Void> sendMessage(Message b) {
Session session = this.session;
if (session == null) {
throw new IllegalStateException("The mms connection has not been enabled.");
} else {
CompletableFuture<Void> result = new CompletableFuture<>();
// Save message for later sending
session.sendMessage(b, result);
return result;
}
}
public void setEnabled(boolean isEnabled) {
LOGGER.debug(isEnabled ? "Enabling" : "Disabling" + " connection to the MMS Server");
lock.lock();
try {
if (isEnabled != this.isEnabled) {
this.isEnabled = isEnabled;
if (isEnabled) {
if (session == null) {
session = Session.createNewSessionAndConnect(ctm, clientInfo, sessionListener, connectionListener);
}
} else { // Disable
if (session != null) { // only disconnect if we have an active session
session.closeSession(MmsConnectionClosingCode.NORMAL);
}
}
stateChange.signalAll();
}
} finally {
lock.unlock();
}
}
@RunOnStop
public void shutdown() {
setEnabled(false);
}
@SuppressWarnings("unchecked")
public <T> void subscribe(Class<? extends T> messageType, BiConsumer<MmsMessage, T> consumer) {
LOGGER.debug("Subscribing " + consumer + " to instances of " + messageType);
subscribers.add((m) -> {
Message e = m.getMessage();
if (messageType.isAssignableFrom(e.getClass())) {
consumer.accept(m, (T) e);
}
});
}
void disconnectedOrConnected() {
lock.lock();
try {
stateChange.signalAll();
} finally {
lock.unlock();
}
}
}