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