Package com.kurento.kmf.jsonrpcconnector.client

Source Code of com.kurento.kmf.jsonrpcconnector.client.JsonRpcClientWebSocket

/*
* (C) Copyright 2013 Kurento (http://kurento.org/)
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package com.kurento.kmf.jsonrpcconnector.client;

import static com.kurento.kmf.jsonrpcconnector.JsonUtils.fromJson;
import static com.kurento.kmf.jsonrpcconnector.JsonUtils.fromJsonRequest;
import static com.kurento.kmf.jsonrpcconnector.JsonUtils.fromJsonResponse;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.WebSocketConnectionManager;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.kurento.kmf.common.exception.KurentoException;
import com.kurento.kmf.jsonrpcconnector.TransportException;
import com.kurento.kmf.jsonrpcconnector.internal.JsonRpcConstants;
import com.kurento.kmf.jsonrpcconnector.internal.JsonRpcRequestSenderHelper;
import com.kurento.kmf.jsonrpcconnector.internal.client.ClientSession;
import com.kurento.kmf.jsonrpcconnector.internal.client.TransactionImpl.ResponseSender;
import com.kurento.kmf.jsonrpcconnector.internal.message.MessageUtils;
import com.kurento.kmf.jsonrpcconnector.internal.message.Request;
import com.kurento.kmf.jsonrpcconnector.internal.message.Response;
import com.kurento.kmf.jsonrpcconnector.internal.ws.PendingRequests;
import com.kurento.kmf.jsonrpcconnector.internal.ws.WebSocketResponseSender;

public class JsonRpcClientWebSocket extends JsonRpcClient {

  private final Logger log = LoggerFactory
      .getLogger(JsonRpcClientWebSocket.class);

  private ExecutorService execService = Executors.newFixedThreadPool(10);

  private String url;
  private volatile WebSocketSession wsSession;
  private final PendingRequests pendingRequests = new PendingRequests();
  private final HttpHeaders headers = new HttpHeaders();

  private ResponseSender rs;

  private static final long TIMEOUT = 10000;

  public JsonRpcClientWebSocket(String url) {
    this(url, new HttpHeaders());
  }

  public JsonRpcClientWebSocket(String url, HttpHeaders headers) {

    // Append /ws to avoid collisions with http
    // Append /websockets to point to websocket interface in SockJS
    this.url = url + "/ws/websocket";

    rsHelper = new JsonRpcRequestSenderHelper() {
      @Override
      public <P, R> Response<R> internalSendRequest(Request<P> request,
          Class<R> resultClass) throws IOException {

        return internalSendRequestWebSocket(request, resultClass);
      }

      @Override
      protected void internalSendRequest(
          Request<? extends Object> request,
          Class<JsonElement> resultClass,
          Continuation<Response<JsonElement>> continuation) {

        internalSendRequestWebSocket(request, resultClass, continuation);
      }
    };

    this.headers.putAll(headers);
  }

  protected void internalSendRequestWebSocket(
      final Request<? extends Object> request,
      final Class<JsonElement> resultClass,
      final Continuation<Response<JsonElement>> continuation) {

    // FIXME: Poor man async implementation.
    execService.submit(new Runnable() {
      @Override
      public void run() {
        try {
          Response<JsonElement> result = internalSendRequestWebSocket(
              request, resultClass);
          try {
            continuation.onSuccess(result);
          } catch (Exception e) {
            log.error("Exception while processing response", e);
          }
        } catch (Exception e) {
          continuation.onError(e);
        }
      }
    });
  }

  private synchronized void connectIfNecessary() throws IOException {

    if (wsSession == null) {

      final CountDownLatch latch = new CountDownLatch(1);

      TextWebSocketHandler webSocketHandler = new TextWebSocketHandler() {

        @Override
        public void afterConnectionEstablished(
            WebSocketSession wsSession2) throws Exception {

          wsSession = wsSession2;
          rs = new WebSocketResponseSender(wsSession);
          latch.countDown();
        }

        @Override
        public void handleTextMessage(WebSocketSession session,
            TextMessage message) throws Exception {
          handleWebSocketTextMessage(message);
        }

        @Override
        public void afterConnectionClosed(WebSocketSession s,
            CloseStatus status) throws Exception {

          // TODO Call this when you can't reconnect or close is
          // issued by client.
          handlerManager.afterConnectionClosed(session,
              status.getReason());
          log.debug("WebSocket closed due to: {}", status);
          wsSession = null;
          // TODO Start a timer to force reconnect in x millis
          // For the moment we are going to force it sending another
          // message.
        }
      };

      WebSocketConnectionManager connectionManager = new WebSocketConnectionManager(
          new StandardWebSocketClient(), webSocketHandler, url);

      connectionManager.setHeaders(headers);
      connectionManager.start();

      try {
        // FIXME: Make this configurable and search a way to detect the
        // underlying connection timeout
        if (!latch.await(10, TimeUnit.SECONDS)) {
          throw new KurentoException(
              "Timeout of 10s when waiting to connect to Websocket server");
        }

        if (session == null) {

          session = new ClientSession(null, null,
              JsonRpcClientWebSocket.this);
          handlerManager.afterConnectionEstablished(session);

        } else {

          String result = rsHelper.sendRequest(
              JsonRpcConstants.METHOD_RECONNECT, String.class);

          log.info("Reconnection result: {}", result);

        }

      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  private void handleWebSocketTextMessage(TextMessage message)
      throws IOException {

    JsonObject jsonMessage = fromJson(message.getPayload(),
        JsonObject.class);

    if (jsonMessage.has(JsonRpcConstants.METHOD_PROPERTY)) {
      handleRequestFromServer(jsonMessage);
    } else {
      handleResponseFromServer(jsonMessage);
    }
  }

  private void handleRequestFromServer(final JsonObject message)
      throws IOException {

    // TODO: Think better ways to do this:
    // handleWebSocketTextMessage seems to be sequential. That is, the
    // message waits to be processed until previous message is being
    // processed. This behavior doesn't allow made a new request in the
    // handler of an event. To avoid this problem, we have decided to
    // process requests from server in a new thread (reused from
    // ExecutorService).
    execService.submit(new Runnable() {
      @Override
      public void run() {
        try {
          handlerManager.handleRequest(session,
              fromJsonRequest(message, JsonElement.class), rs);
        } catch (IOException e) {
          log.warn("Exception processing request " + message, e);
        }
      }
    });
  }

  private void handleResponseFromServer(JsonObject message) {

    Response<JsonElement> response = fromJsonResponse(message,
        JsonElement.class);

    setSessionId(response.getSessionId());

    pendingRequests.handleResponse(response);
  }

  private <P, R> Response<R> internalSendRequestWebSocket(Request<P> request,
      Class<R> resultClass) throws IOException {

    connectIfNecessary();

    Future<Response<JsonElement>> responseFuture = null;

    if (request.getId() != null) {
      responseFuture = pendingRequests.prepareResponse(request.getId());
    }

    String jsonMessage = request.toString();
    log.debug("Req-> {}", jsonMessage.trim());
    synchronized (wsSession) {
      wsSession.sendMessage(new TextMessage(jsonMessage));
    }

    if (responseFuture == null) {
      return null;
    }

    Response<JsonElement> responseJson;
    try {

      responseJson = responseFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);

      log.debug("<-Res {}", responseJson.toString());

      Response<R> response = MessageUtils.convertResponse(responseJson,
          resultClass);

      if (response.getSessionId() != null) {
        session.setSessionId(response.getSessionId());
      }

      return response;

    } catch (InterruptedException e) {
      // TODO What to do in this case?
      throw new KurentoException(
          "Interrupted while waiting for a response", e);
    } catch (ExecutionException e) {
      // TODO Is there a better way to handle this?
      throw new KurentoException("This exception shouldn't be thrown", e);
    } catch (TimeoutException e) {
      throw new TransportException("Timeout of " + TIMEOUT
          + " seconds waiting from response", e);
    }
  }

  @Override
  public void close() throws IOException {
    if (wsSession != null) {
      wsSession.close();
    }
  }

  public WebSocketSession getWebSocketSession() {
    return wsSession;
  }
}
TOP

Related Classes of com.kurento.kmf.jsonrpcconnector.client.JsonRpcClientWebSocket

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.