Package org.jboss.errai.bus.client.framework.transports

Source Code of org.jboss.errai.bus.client.framework.transports.SSEHandler

/*
* Copyright 2012 JBoss, by Red Hat, Inc
*
* 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 org.jboss.errai.bus.client.framework.transports;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.jboss.errai.bus.client.api.Subscription;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.client.api.messaging.MessageCallback;
import org.jboss.errai.bus.client.framework.BusState;
import org.jboss.errai.bus.client.framework.ClientMessageBusImpl;
import org.jboss.errai.bus.client.util.BusToolsCli;
import org.jboss.errai.common.client.util.LogUtil;

import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Timer;

/**
* @author Mike Brock
*/
public class SSEHandler implements TransportHandler, TransportStatistics {
  private static final String SSE_AGENT_SERVICE = "SSEAgent";

  private final ClientMessageBusImpl clientMessageBus;
  private final MessageCallback messageCallback;

  private final HttpPollingHandler pollingHandler;
  private String sseEntryPoint;

  private int rxCount;
  private long connectedTime = -1;

  private boolean stopped;
  private boolean connected;

  /**
   * The number of reconnect attempts that have been made since a message was
   * received over the SSE channel.
   */
  private int retries;

  private boolean configured;
  private boolean hosed;

  private String unsupportedReason = UNSUPPORTED_MESSAGE_NO_SERVER_SUPPORT;

  private final Timer pingTimeout = new Timer() {
    @Override
    public void run() {
      if (!connected) {
        LogUtil.log(this + ": initial timeout expired");
        notifyDisconnected();
      }
    }
  };

  private Object sseChannel;

  /**
   * Bus subscription that receives ping responses from the server bus. This is
   * used for verifying that the SSE channel is actually working.
   */
  private Subscription sseAgentSubscription;

  public SSEHandler(final MessageCallback messageCallback, final ClientMessageBusImpl clientMessageBus) {
    this.clientMessageBus = clientMessageBus;
    this.messageCallback = messageCallback;
    this.pollingHandler = HttpPollingHandler.newNoPollingInstance(messageCallback, clientMessageBus);

    sseAgentSubscription = clientMessageBus.subscribe(SSE_AGENT_SERVICE, new MessageCallback() {
      @Override
      public void callback(final Message message) {
        notifyConnected();
      }
    });
  }

  @Override
  public void configure(final Message capabilitiesMessage) {
    configured = true;

    if (!isSSESupported()) {
      hosed = true;
      unsupportedReason = UNSUPPORTED_MESSAGE_NO_SERVER_SUPPORT;
      LogUtil.log("this browser does not support SSE");
      return;
    }

    this.sseEntryPoint = URL.encode(clientMessageBus.getApplicationLocation(clientMessageBus.getInServiceEntryPoint()))
        + "?z=0000&sse=1&clientId=" + URL.encodePathSegment(clientMessageBus.getClientId());

  }

  @Override
  public void start() {
    stopped = false;
    if (connected) {
      LogUtil.log("did not start SSE handler: already started.");
      return;
    }
    sseChannel = attemptSSEChannel(clientMessageBus, sseEntryPoint);
  }

  @Override
  public Collection<Message> stop(final boolean stopAllCurrentRequests) {
    stopped = true;
    if (sseChannel != null) {
      disconnect(sseChannel);
      sseChannel = null;
    }
    return pollingHandler.stop(stopAllCurrentRequests);
  }

  @Override
  public void transmit(final List<Message> txMessages) {
    this.pollingHandler.transmit(txMessages);
  }

  @Override
  public void handleProtocolExtension(final Message message) {
  }

  @Override
  public boolean isUsable() {
    return !hosed && configured;
  }

  private void handleReceived(final String json) {
    rxCount++;
    BusToolsCli.decodeToCallback(json, messageCallback);
  }

  private static native void disconnect(Object channel) /*-{
      channel.close();
  }-*/;

  private native boolean isSSESupported() /*-{
      return !!window.EventSource;
  }-*/;

  private native Object attemptSSEChannel(final ClientMessageBusImpl bus, final String sseAddress) /*-{
      var thisRef = this;

      var errorHandler = function (e) {
          $wnd.console.log("SSE channel error (according to the browser)");
          thisRef.@org.jboss.errai.bus.client.framework.transports.SSEHandler::verifyConnected()();
      };

      var openHandler = function () {
          $wnd.console.log("SSE channel opened (according to the browser)");
          thisRef.@org.jboss.errai.bus.client.framework.transports.SSEHandler::verifyConnected()();
      };

      var sseSource = new EventSource(sseAddress);
      sseSource.addEventListener('message', function (e) {
          thisRef.@org.jboss.errai.bus.client.framework.transports.SSEHandler::handleReceived(Ljava/lang/String;)(e.data);
      }, false);

      sseSource.onerror = errorHandler;
      sseSource.onopen = openHandler;

      return sseSource;
  }-*/;

  /**
   * Sends a ping request to the server. If the ping response is not received
   * within a reasonable time limit, notifyDisconnected() will be called.
   */
  private void verifyConnected() {

    // in case we were in the middle of something already
    pingTimeout.cancel();

    transmit(Collections.singletonList(MessageBuilder.createMessage()
        .toSubject("ServerEchoService")
        .signalling().done().repliesToSubject(SSE_AGENT_SERVICE).getMessage()));

    pingTimeout.schedule(2500);
  }

  private void notifyConnected() {
    pingTimeout.cancel();
    retries = 0;

    if (!connected) {
      connected = true;
      connectedTime = System.currentTimeMillis();
      LogUtil.log(this + ": SSE channel is active.");
    }

    if (clientMessageBus.getState() == BusState.CONNECTION_INTERRUPTED) {
      clientMessageBus.setState(BusState.CONNECTED);
    }
  }

  private void notifyDisconnected() {
    connected = false;

    pingTimeout.cancel();
    LogUtil.log(this + " channel disconnected.");
    connectedTime = -1;
    clientMessageBus.setState(BusState.CONNECTION_INTERRUPTED);

    disconnect(sseChannel);

    if (!stopped) {
      retries++;
      start();
    }
  }

  @Override
  public String toString() {
    return "SSE[" + System.identityHashCode(this) + "]";
  }

  @Override
  public TransportStatistics getStatistics() {
    return this;
  }

  @Override
  public String getTransportDescription() {
    return "HTTP + Server-Sent Events";
  }

  @Override
  public String getUnsupportedDescription() {
    return unsupportedReason;
  }

  @Override
  public int getMessagesSent() {
    return pollingHandler.getMessagesSent();
  }

  @Override
  public int getMessagesReceived() {
    return rxCount;
  }

  @Override
  public long getConnectedTime() {
    return connectedTime;
  }

  @Override
  public int getMeasuredLatency() {
    return pollingHandler.getMeasuredLatency();
  }

  @Override
  public long getLastTransmissionTime() {
    return pollingHandler.getLastTransmissionTime();
  }

  @Override
  public boolean isFullDuplex() {
    return false;
  }

  @Override
  public String getRxEndpoint() {
    return clientMessageBus.getInServiceEntryPoint();
  }

  @Override
  public String getTxEndpoint() {
    return clientMessageBus.getOutServiceEntryPoint();
  }

  @Override
  public int getPendingMessages() {
    return pollingHandler.getStatistics().getPendingMessages();
  }

  @Override
  public void close() {
    if (!stopped) {
      stop(true);
    }
    sseAgentSubscription.remove();
    configured = false;
  }
}
TOP

Related Classes of org.jboss.errai.bus.client.framework.transports.SSEHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.