All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.byu.hbll.box.client.BoxUpdatesClient Maven / Gradle / Ivy

The newest version!
package edu.byu.hbll.box.client;

import edu.byu.hbll.box.internal.util.UriBuilder;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;

/**
 * Listens for updates inside a Box and runs the given runnable when there is one. Note that Box
 * only sends out update signals at most once per second.
 *
 * @author Charles Draper
 */
public class BoxUpdatesClient {

  private static final Logger logger = Logger.getLogger(BoxUpdatesClient.class.getName());

  private static final int MIN_RETRY_TIMEOUT = 1000;
  private static final int MAX_RETRY_TIMEOUT = 120000;

  private Runnable runnable;
  private URI uri;
  private ThreadFactory threadFactory;
  private volatile int retryTimeout;

  /**
   * Creates a new {@link BoxUpdatesClient} that will communicate with an upstream box found at the
   * given base uri and run the given runnable whenever there are updates detected.
   *
   * @param uri the base uri of the box source (eg, http://localhost:8080/app/box)
   * @param runnable the runnable to run when there are updates
   */
  public BoxUpdatesClient(URI uri, Runnable runnable) {
    this(uri, runnable, Executors.defaultThreadFactory());
  }

  /**
   * Creates a new {@link BoxUpdatesClient} that will communicate with an upstream box found at the
   * given base uri and run the given runnable whenever there are updates detected.
   *
   * @param uri the base uri of the box source (eg, http://localhost:8080/app/box)
   * @param runnable the runnable to run when there are updates
   * @param threadFactory the thread factory to use for creating the thread that keeps the
   *     connection alive
   */
  public BoxUpdatesClient(URI uri, Runnable runnable, ThreadFactory threadFactory) {
    this.runnable = runnable;

    String httpScheme = uri.getScheme();
    String wsScheme = httpScheme.equals("https") ? "wss" : "ws";
    this.uri = new UriBuilder(uri).scheme(wsScheme).path("updates").build();

    this.threadFactory = threadFactory;
    this.threadFactory.newThread(() -> connect()).start();
  }

  private class WebSocketListener implements WebSocket.Listener {

    @Override
    public void onOpen(WebSocket webSocket) {
      webSocket.request(1);
    }

    @Override
    public CompletionStage onText(WebSocket webSocket, CharSequence message, boolean last) {
      webSocket.request(1);

      try {
        runnable.run();
      } catch (Exception e) {
        logger.warning("unabled to run harvest for updates from " + uri + ": " + e);
      }

      return null;
    }

    @Override
    public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) {
      logger.warning(
          "WebSockets connection with " + uri + " closed unexpectedly. Attempting to reconnect.");
      webSocket.abort();
      connect();
      return null;
    }

    @Override
    public void onError(WebSocket webSocket, Throwable error) {
      logger.warning("WebSockets connection error with " + uri + ". Attempting to reconnect.");
      webSocket.abort();
      connect();
    }
  }

  private void connect() {
    while (true) {
      try {
        HttpClient.newBuilder()
            .executor(Executors.newCachedThreadPool(threadFactory))
            .build()
            .newWebSocketBuilder()
            .buildAsync(uri, new WebSocketListener())
            .join();

        logger.info("WebSockets connection established with " + uri + ".");
        retryTimeout = 0;
        break;
      } catch (Exception e) {
        logger.warning(
            "Attempt to establish WebSockets connection with "
                + uri
                + " failed. Trying again. "
                + e);
        retryTimeout = Math.max(MIN_RETRY_TIMEOUT, Math.min(MAX_RETRY_TIMEOUT, retryTimeout * 2));
      }

      try {
        Thread.sleep(retryTimeout);
      } catch (InterruptedException e) {
        return;
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy