/* 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.mms.server.connection.client;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.maritimecloud.internal.mms.messages.Connected;
import net.maritimecloud.internal.mms.messages.PositionReport;
import net.maritimecloud.internal.mms.messages.spi.MmsMessage;
import net.maritimecloud.message.Message;
import net.maritimecloud.mms.server.connection.transport.ServerTransport;
import net.maritimecloud.mms.server.endpoints.ServerClientEndpointManager;
import net.maritimecloud.net.mms.MmsConnectionClosingCode;
import net.maritimecloud.util.geometry.PositionTime;
/**
*
* @author Kasper Nielsen
*/
public class Client {
/** The client manager that manages this client. */
final ClientManager clientManager;
/** A holder of various client properties. */
private final ClientProperties clientProperties = new ClientProperties("Unknown", "Unknown", "Unknown");
final ServerClientEndpointManager endpointManager = new ServerClientEndpointManager(this);
/** The unique if of the client. */
private final String id;
volatile PositionTime latestPositionAndTime;
/** A read write lock for the client. */
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/** The current state of this client. */
volatile ClientInternalState state;
volatile long timeOfLatestReceivedMessage = System.nanoTime();
public Client(ClientManager manager, ServerTransport initialTransport, String id) {
this.clientManager = requireNonNull(manager);
this.id = id;
this.state = new ClientInternalState(State.CONNECTING, initialTransport, null);
}
public void close(MmsConnectionClosingCode closingCode) {
lock.writeLock().lock();
try {
ClientInternalState state = this.state;
if (state.state != State.TERMINATED) {
state.transport.close(closingCode);
}
} finally {
lock.writeLock().unlock();
}
}
Client connectWithWriteLock(ServerTransport transport) {
Session session = new Session(this);
state = new ClientInternalState(State.CONNECTED, transport, session);
MmsMessage mm = new MmsMessage(new Connected().setSessionId(session.getSessionId())
.setLastReceivedMessageId(0L));
transport.sendMessage(mm); // Send connected message
session.onConnectWithWriteLock(transport, 0);
return this;
}
SessionMessageFuture sendMessage(Session requireSession, Message m) {
lock.readLock().lock();
try {
ClientInternalState state = this.state;
if (state.state == State.CONNECTING || state.state == State.TERMINATED) {
return SessionMessageFuture.notConnected(m);
}
final Session session = state.session;
if (requireSession != null && requireSession != session) {
return SessionMessageFuture.wrongSession(m);
}
return session.enqueueMessageWithReadLock(m);
} finally {
lock.readLock().unlock();
}
}
/**
* @return the clientProperties
*/
public ClientProperties getClientProperties() {
return clientProperties;
}
/**
* @return the endpointManager
*/
public ServerClientEndpointManager getEndpointManager() {
return endpointManager;
}
/**
* Returns the id of the client.
*
* @return the id of the client
*/
public String getId() {
return id;
}
/**
* @return the latest
*/
public PositionTime getLatestPositionAndTime() {
return latestPositionAndTime;
}
/**
* @return the latestReceivedMessage
*/
public long getTimeOfLatestReceivedMessage() {
return timeOfLatestReceivedMessage;
}
/**
* Returns whether or not there is active websocket to the client.
*
* @return whether or not there is active websocket to the client
*/
public boolean isConnected() {
return state.state == State.CONNECTED;
}
// Invoked when socket closes.
// This can happen for a number of reasons.
// The client closed it normally
// The closed the server exception or normally
// No matter what this method is always invoked.
void onClose(ServerTransport t, MmsConnectionClosingCode closingCode) {
lock.writeLock().lock();
try {
ClientInternalState state = this.state;
if (state.state == State.CONNECTED) {
if (closingCode.getId() == 1000) {
this.state = ClientInternalState.TERMINATED;
clientManager.clients.remove(id, this);
} else {
this.state = new ClientInternalState(State.DISCONNECTED, t, state.session);
}
state.session.disconnectedWithWriteLock(closingCode.getId() == 1000);
} else if (state.state == State.CONNECTING) {
this.state = ClientInternalState.TERMINATED;
clientManager.clients.remove(id, this);
} else {
throw new IllegalStateException();
}
} finally {
lock.writeLock().unlock();
}
}
void onMessage(ServerTransport t, MmsMessage message) {
timeOfLatestReceivedMessage = System.nanoTime();
lock.readLock().lock();
try {
ClientInternalState state = this.state;
if (state.transport == t && state.state == State.CONNECTED) {
if (message.getM() instanceof PositionReport) {
PositionTime pt = ((PositionReport) message.getM()).getPositionTime();
// Should we close the client if going back in time??? Think it can happen
// not for a single session, but inbetween sessions.
if (pt.getTime() > latestPositionAndTime.getTime()) {
latestPositionAndTime = pt;
}
}
state.session.onMessageWithReadLock(message);
}
} finally {
lock.readLock().unlock();
}
}
public SessionMessageFuture send(Message message) {
return clientManager.sendMessage(id, message);
}
enum State {
CONNECTED, CONNECTING, DISCONNECTED, TERMINATED;
}
}