package org.elasticsearch.plugin.degraphmalizer.updater;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
/**
* This class handles Change instances. The class can be configured via elasticsearch.yml (see README.md for
* more information). The Updater manages a queue of Change objects, executes HTTP requests for these
* changes and retries changes when HTTP requests fail.
*/
public final class Updater implements Runnable {
private static final ESLogger LOG = Loggers.getLogger(Updater.class);
private static final int NAPTIME = 5 * 1000;
private final HttpClient httpClient = new DefaultHttpClient();
private final String uriScheme;
private final String uriHost;
private final int uriPort;
private final long retryDelayOnFailureInMillis;
private final int maxRetries;
private final String index;
private File errorFile;
private UpdaterQueue queue;
private boolean shutdownInProgress = false;
private boolean sending = false;
public Updater(final String index, final String uriScheme, final String uriHost, final int uriPort, final long retryDelayOnFailureInMillis, final String logPath, final int queueLimit, final int maxRetries) {
this.index = index;
this.uriScheme = uriScheme;
this.uriHost = uriHost;
this.uriPort = uriPort;
this.retryDelayOnFailureInMillis = retryDelayOnFailureInMillis;
this.maxRetries = maxRetries;
queue = new UpdaterQueue(logPath, index, queueLimit);
new Thread(queue).start();
errorFile = new File(logPath, index + "-error.log");
LOG.info("Updater instantiated for index {}. Updates will be sent to {}://{}:{}. Retry delay on failure is {} milliseconds.", index, uriScheme, uriHost, uriPort, retryDelayOnFailureInMillis);
LOG.info("Updater will overflow in {} after limit of {} has been reached, messages will be retried {} times ", logPath, queueLimit, maxRetries);
}
public void shutdown() {
shutdownInProgress = true;
}
public int getQueueSize() {
return queue.size();
}
public void flushQueue() {
queue.clear();
}
public void stopSending() {
sending = false;
}
public void startSending() {
sending = true;
}
public void run() {
try {
boolean done = false;
while (!done) {
if (sending) {
final Change change = queue.take().thing();
perform(change);
} else {
Thread.sleep(NAPTIME);
}
if (shutdownInProgress) {
queue.shutdown();
done = true;
}
}
} catch (Exception e) {
LOG.error("Updater for index {} stopped with exception: {}", index, e);
} finally {
httpClient.getConnectionManager().shutdown();
queue.shutdown();
LOG.info("Updater stopped for index {}.", index);
}
}
public void add(final Change change) {
queue.add(DelayedImpl.immediate(change));
LOG.trace("Received {}", change);
}
private void perform(final Change change) {
final HttpRequestBase request = toRequest(change);
try {
final HttpResponse response = httpClient.execute(request);
if (!isSuccessful(response)) {
LOG.warn("Request {} {} was not successful. Response status code: {}.", request.getMethod(), request.getURI(), response.getStatusLine().getStatusCode());
retry(change);
} else {
LOG.debug("Change performed: {}", change);
}
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
LOG.warn("Error executing request {} {}: {}", request.getMethod(), request.getURI(), e.getMessage());
retry(change);
}
}
private HttpRequestBase toRequest(final Change change) {
final HttpRequestBase request;
final Action action = change.action();
switch (action) {
case UPDATE:
request = new HttpGet(buildURI(change));
break;
case DELETE:
request = new HttpDelete(buildURI(change));
break;
default:
throw new RuntimeException("Unknown action " + action + " for " + change + " on index " + index);
}
return request;
}
private URI buildURI(final Change change) {
final String type = change.type();
final String id = change.id();
final long version = change.version();
final String path = String.format("/%s/%s/%s/%d", index, type, id, version);
try {
return new URIBuilder()
.setScheme(uriScheme)
.setHost(uriHost)
.setPort(uriPort)
.setPath(path)
.build();
} catch (URISyntaxException e) {
throw new RuntimeException("Unexpected error building uri for change " + change + " on index " + index, e);
}
}
private boolean isSuccessful(final HttpResponse response) {
final int statusCode = response.getStatusLine().getStatusCode();
return statusCode == 200;
}
private void retry(final Change change) {
if (change.retries() < maxRetries) {
change.retried();
final DelayedImpl<Change> delayedChange = new DelayedImpl<Change>(change, change.retries() * retryDelayOnFailureInMillis);
queue.add(delayedChange);
LOG.debug("Retrying change {} on index {} in {} milliseconds", change, index, retryDelayOnFailureInMillis);
} else {
logError(change);
}
}
public void logError(Change change) {
try {
LOG.warn("Writing failed change {} to error log {}", change, errorFile.getCanonicalPath());
final PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(errorFile, true), "UTF-8")));
writer.println(change.toValue());
writer.flush();
writer.close();
} catch (IOException e) {
LOG.error("I/O error: " + e.getMessage());
}
}
}