// Copyright 2012 Google Inc. 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.google.collide.client.communication;
import com.google.collide.client.bootstrap.BootstrapSession;
import com.google.collide.client.status.StatusManager;
import com.google.collide.client.status.StatusMessage;
import com.google.collide.client.status.StatusMessage.MessageType;
import com.google.collide.client.util.logging.Log;
import com.google.collide.clientlibs.vertx.VertxBus;
import com.google.collide.clientlibs.vertx.VertxBus.ReplyHandler;
import com.google.collide.clientlibs.vertx.VertxBus.ReplySender;
import com.google.collide.clientlibs.vertx.VertxBusImpl;
import com.google.collide.dtogen.client.RoutableDtoClientImpl;
import com.google.collide.dtogen.shared.ServerToClientDto;
import com.google.collide.json.client.Jso;
import com.google.collide.shared.util.ListenerManager;
import com.google.collide.shared.util.ListenerRegistrar;
import com.google.gwt.user.client.Timer;
import java.util.ArrayList;
import java.util.List;
/**
* A PushChannel abstraction on top of the {@link VertxBus}.
*
*/
public class PushChannel {
public interface Listener {
void onReconnectedSuccessfully();
}
public static PushChannel create(MessageFilter messageFilter, StatusManager statusManager) {
// If we do not have a valid client ID... bail.
if (BootstrapSession.getBootstrapSession().getActiveClientId() == null) {
StatusMessage fatal =
new StatusMessage(statusManager, MessageType.FATAL, "You are not logged in!");
fatal.addAction(StatusMessage.RELOAD_ACTION);
fatal.setDismissable(false);
fatal.fire();
return null;
}
VertxBus eventBus = VertxBusImpl.create();
PushChannel pushChannel = new PushChannel(eventBus, messageFilter, statusManager);
pushChannel.init();
return pushChannel;
}
private class DisconnectedTooLongTimer extends Timer {
private static final int DELAY_MS = 60 * 1000;
@Override
public void run() {
// reconnection effort failed.
StatusMessage fatal = new StatusMessage(
statusManager, MessageType.FATAL, "Lost communication with the server.");
fatal.addAction(StatusMessage.RELOAD_ACTION);
fatal.setDismissable(false);
fatal.fire();
}
void schedule() {
schedule(DELAY_MS);
}
}
private class QueuedMessage {
final String address;
final String msg;
final ReplyHandler replyHandler;
QueuedMessage(String address, String msg, ReplyHandler replyHandler) {
this.address = address;
this.msg = msg;
this.replyHandler = replyHandler;
}
}
private final ListenerManager<PushChannel.Listener> listenerManager = ListenerManager.create();
private final DisconnectedTooLongTimer disconnectedTooLongTimer = new DisconnectedTooLongTimer();
private final VertxBus.ConnectionListener connectionListener = new VertxBus.ConnectionListener() {
private boolean hasReceivedOnDisconnected;
private VertxBus.MessageHandler messageHandler = null;
@Override
public void onOpen() {
// Lazily initialize the messageHandler and register to handle messages.
if (messageHandler == null) {
messageHandler = new VertxBus.MessageHandler() {
@Override
public void onMessage(String message, ReplySender replySender) {
ServerToClientDto dto =
(ServerToClientDto) Jso.deserialize(message).<RoutableDtoClientImpl>cast();
messageFilter.dispatchMessage(dto);
}
};
eventBus.register(
"client." + BootstrapSession.getBootstrapSession().getActiveClientId(), messageHandler);
}
// Notify listeners who handle reconnections.
if (hasReceivedOnDisconnected) {
disconnectedTooLongTimer.cancel();
listenerManager.dispatch(new ListenerManager.Dispatcher<PushChannel.Listener>() {
@Override
public void dispatch(PushChannel.Listener listener) {
listener.onReconnectedSuccessfully();
}
});
hasReceivedOnDisconnected = false;
}
// Drain any messages that came in while the channel was not open.
for (QueuedMessage msg : queuedMessages) {
eventBus.send(msg.address, msg.msg, msg.replyHandler);
}
queuedMessages.clear();
}
@Override
public void onClose() {
hasReceivedOnDisconnected = true;
disconnectedTooLongTimer.schedule();
}
};
private final MessageFilter messageFilter;
private final StatusManager statusManager;
private final VertxBus eventBus;
private final List<QueuedMessage> queuedMessages = new ArrayList<QueuedMessage>();
private PushChannel(VertxBus eventBus, MessageFilter messageFilter, StatusManager statusManager) {
this.eventBus = eventBus;
this.messageFilter = messageFilter;
this.statusManager = statusManager;
}
private void init() {
eventBus.setOnOpenCallback(connectionListener);
eventBus.setOnCloseCallback(connectionListener);
}
/**
* Sends a message to an address, providing an replyHandler.
*/
public void send(String address, String message, ReplyHandler replyHandler) {
if (eventBus.getReadyState() != VertxBus.OPEN) {
Log.debug(PushChannel.class,
"Message sent to '" + address + "' while channel was disconnected: " + message);
queuedMessages.add(new QueuedMessage(address, message, replyHandler));
return;
}
eventBus.send(address, message, replyHandler);
}
/**
* Sends a message to an address.
*/
public void send(String address, String message) {
send(address, message, null);
}
public ListenerRegistrar<PushChannel.Listener> getListenerRegistrar() {
return listenerManager;
}
}