/* 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.session;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import net.maritimecloud.internal.mms.client.ClientInfo;
import net.maritimecloud.internal.mms.client.connection.transport.ClientTransport;
import net.maritimecloud.internal.mms.messages.Connected;
import net.maritimecloud.internal.mms.messages.Hello;
import net.maritimecloud.internal.mms.messages.Welcome;
import net.maritimecloud.internal.mms.messages.spi.MmsMessage;
import net.maritimecloud.internal.util.logging.Logger;
import net.maritimecloud.message.Message;
import net.maritimecloud.net.mms.MmsConnectionClosingCode;
import net.maritimecloud.util.Binary;
import net.maritimecloud.util.geometry.PositionTime;
/**
*
* @author Kasper Nielsen
*/
final class SessionStateConnecting extends SessionState {
/** The logger. */
static final Logger LOG = Logger.get(SessionStateConnecting.class);
/** A count down latch indicated if the connection process has been cancelled. */
final CountDownLatch cancel = new CountDownLatch(1);
/** Whether or not we have received a hello message. */
private boolean receivedHelloMessage /* = false */;
final ClientTransport transport;
/** The URI to connect to. */
final URI uri;
final Thread connectThread = new Thread(() -> run0(), "MMS-ConnectThread");
final ClientInfo info;
/**
* @param session
*/
SessionStateConnecting(Session session, ClientInfo info) {
super(session);
this.uri = requireNonNull(info.getServerURI());
this.info = info;
transport = session.ctm.create(session.tl, session.connectionListener);
connectThread.setDaemon(true);
}
void cancelWhileFullyLocked(MmsConnectionClosingCode reason) {
cancel.countDown();
connectThread.interrupt();
transport.closeTransport(reason);
try {
connectThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
session.listener.onSessionClose(reason);
}
void connectAsynchronously() {
connectThread.start();
}
void run0() {
// To avoid a situation where we constantly reconnect
// we make sure that there is at lease 2000 milliseconds
long awaitMillis = Math.max(
ClientInfo.RECONNECT_TIME_DELAY - (System.currentTimeMillis() - info.getLatestConnectionAttempt()), 0);
awaitMillis = 0;
if (awaitMillis > 0) {
LOG.info("Sleeping for " + awaitMillis + " ms before connecting again.");
try {
Thread.sleep(awaitMillis);
} catch (InterruptedException ignore) {}
}
LOG.info("Trying to connect to " + uri);
session.connectionListener.connecting(uri);
while (this == session.state && cancel.getCount() > 0) {
try {
info.setLatestConnectionAttempt(System.currentTimeMillis());
transport.connectBlocking(uri);
return;
} catch (IllegalStateException e) {
LOG.error("A serious internal error", e);
} catch (IOException e) {
if (cancel.getCount() > 0) {// Only log the error if we are not cancelled
LOG.error("Could not connect to " + uri + ", will try again later");
}
}
try {
cancel.await(2, TimeUnit.SECONDS); // exponential backoff?
} catch (InterruptedException ignore) {}
}
}
public void onMessage(MmsMessage pm) {
Message m = pm.getM();
String err = null;
if (!receivedHelloMessage) {
if (m instanceof Welcome) {
onWelcome((Welcome) m);
} else {
err = "Expected a welcome message, but was: " + m.getClass().getSimpleName();
}
} else if (m instanceof Connected) {
onConnected((Connected) m);
} else {
err = "Expected a connected message, but was: " + m.getClass().getSimpleName();
}
if (err != null) {
LOG.error(err);
transport.closeTransport(MmsConnectionClosingCode.WRONG_MESSAGE.withMessage(err));
// TODO Kill session
}
}
private void onConnected(Connected cm) {
Binary b = session.sessionId;
SessionStateConnected.connected(this, b, cm.getSessionId(),
cm.getLastReceivedMessageId() == null ? 0 : cm.getLastReceivedMessageId());
}
private void onWelcome(Welcome w) {
Hello h = new Hello();
// Client properties
h.setClientId(session.info.getClientId().toString());
if (session.info.getClientConnectString() != null) {
for (Map.Entry<String, String> e : session.info.getClientConnectString().entrySet()) {
h.putProperties(e.getKey(), e.getValue());
}
}
// Reconnect if we have an ongoing session
if (session.sessionId != null) { // reconnecting or not
h.setSessionId(session.sessionId);
h.setLastReceivedMessageId(session.latestReceivedId);
}
// Set current position
Optional<PositionTime> pr = session.info.getCurrentPosition();
if (pr.isPresent()) {
h.setPositionTime(pr.get().withTime(System.currentTimeMillis()));
}
transport.sendMessage(new MmsMessage(h));
receivedHelloMessage = true;
}
}