package com.gratex.perconik.uaca;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
import com.gratex.perconik.uaca.preferences.UacaOptions;
import com.gratex.perconik.uaca.preferences.UacaPreferences;
import sk.stuba.fiit.perconik.data.providers.MapperProvider;
import sk.stuba.fiit.perconik.utilities.concurrent.TimeValue;
import static java.lang.String.format;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static java.util.concurrent.TimeUnit.SECONDS;
import static javax.ws.rs.client.ClientBuilder.newClient;
import static javax.ws.rs.core.Response.Status.Family.CLIENT_ERROR;
import static javax.ws.rs.core.Response.Status.Family.SERVER_ERROR;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.toDefaultString;
import static sk.stuba.fiit.perconik.utilities.concurrent.TimeValue.of;
public class SharedUacaProxy extends AbstractUacaProxy {
private static final String connectionCheckPath = "ide/checkin";
private static final TimeValue waitBeforeClientClose = of(12, SECONDS);
protected final UacaOptions options;
private final UacaReporter reporter;
private final SharedSecrets secrets;
public SharedUacaProxy() {
this(UacaPreferences.getShared());
}
public SharedUacaProxy(final UacaOptions options) {
this.options = checkNotNull(options);
this.reporter = new UacaReporter(options);
this.secrets = SharedSecrets.obtain(this.reporter);
}
public static final void checkConnection(final String url) {
newClient().target(url).path(connectionCheckPath).request().options().close();
}
public static final void checkConnection(final URL url) {
checkConnection(url.toString());
}
private static final class SharedSecrets {
private static final SharedSecrets instance = new SharedSecrets();
private long counter = 0L;
private Client client;
private SharedSecrets() {}
static SharedSecrets obtain(final UacaReporter reporter) {
synchronized (instance) {
return instance.connect(reporter);
}
}
private SharedSecrets connect(final UacaReporter reporter) {
long count = ++ this.counter;
assert count > 0;
reporter.logNotice(format("connect -> %d connections", count));
return this;
}
private SharedSecrets disconnect(final UacaReporter reporter, final TimeValue wait) {
long count = -- this.counter;
assert count >= 0;
reporter.logNotice(format("disconnect -> %d connections", count));
if (count == 0L && this.client != null) {
close(reporter, this.client, wait);
}
return this;
}
private static Client open(final UacaReporter reporter) {
reporter.logNotice(format("opening shared client"));
try {
Client client = newClient().register(MapperProvider.class);
reporter.logNotice(format("shared client opened -> %s", toDefaultString(client)));
return client;
} catch (Exception failure) {
reporter.logError("unable to open shared client", failure);
throw failure;
}
}
private static void close(final UacaReporter reporter, final Client client, final TimeValue wait) {
assert client != null;
checkNotNull(wait);
reporter.logNotice(format("closing shared client -> %s", toDefaultString(client)));
ExecutorService service = newSingleThreadExecutor();
service.execute(new Runnable() {
public void run() {
sleepUninterruptibly(wait.duration(), wait.unit());
try {
client.close();
reporter.logNotice("shared client closed");
} catch (Exception failure) {
reporter.logError(format("unable to close shared client -> %s", toDefaultString(client)), failure);
}
}
});
shutdownAndAwaitTermination(service, wait.duration(), wait.unit());
}
synchronized void release(final UacaReporter reporter, final TimeValue wait) {
this.disconnect(reporter, wait);
}
synchronized Client client(final UacaReporter reporter) {
if (this.client == null) {
this.client = open(reporter);
}
return this.client;
}
}
@Override
protected final Client client() {
return this.secrets.client(this.reporter);
}
@Override
protected final URL url() {
return this.options.getApplicationUrl();
}
@Override
protected final void filterRequest(final WebTarget target, @Nullable final Object request) {
this.reporter.logRequest(target, request);
}
@Override
protected final void processResponse(final WebTarget target, @Nullable final Object request, final Response response) {
StatusType status = response.getStatusInfo();
Family family = status.getFamily();
if (family == CLIENT_ERROR || family == SERVER_ERROR) {
String message = format("POST %s -> %s %d %s", target.getUri(), family, status.getStatusCode(), status.getReasonPhrase());
throw new IllegalStateException(message);
}
}
@Override
protected void reportFailure(final String message, final Exception failure) {
this.reporter.logError(message, failure);
this.reporter.displayError(message, failure);
}
public final void close() {
this.secrets.release(this.reporter, waitBeforeClientClose);
this.closeHook();
}
protected void closeHook() {}
}