package sk.stuba.fiit.perconik.elasticsearch.ui.preferences;
import java.util.Map;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
import sk.stuba.fiit.perconik.eclipse.jface.dialogs.MessageDialogWithTextArea;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.WidgetListener;
import sk.stuba.fiit.perconik.elasticsearch.ElasticsearchProxy;
import sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions;
import sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchPreferences;
import sk.stuba.fiit.perconik.ui.Buttons;
import sk.stuba.fiit.perconik.ui.Groups;
import sk.stuba.fiit.perconik.utilities.configuration.MapOptions;
import sk.stuba.fiit.perconik.utilities.configuration.OptionParser;
import static java.lang.String.format;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptionParsers.byteSizeParser;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptionParsers.timeParser;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.clientTransportAddresses;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.clientTransportIgnoreClusterName;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.clientTransportNodesSamplerInterval;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.clientTransportPingTimeout;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.clusterName;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.displayErrors;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.logErrors;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.logNotices;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.networkTcpKeepAlive;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.networkTcpNoDelay;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.networkTcpReceiveBufferSize;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.networkTcpReuseAddress;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.networkTcpSendBufferSize;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.nodeName;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.pathLogs;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.pathWork;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.transportTcpCompress;
import static sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions.Schema.transportTcpConnectTimeout;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.toLowerCase;
import static sk.stuba.fiit.perconik.utilities.configuration.OptionParsers.arrayListParser;
import static sk.stuba.fiit.perconik.utilities.configuration.OptionParsers.inetSocketAddressParser;
import static sk.stuba.fiit.perconik.utilities.configuration.OptionParsers.pathParser;
import static sk.stuba.fiit.perconik.utilities.configuration.OptionParsers.stringParser;
public final class ElasticsearchPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
private final Map<String, FieldEditor> editors;
public ElasticsearchPreferencePage() {
super(GRID);
this.editors = newLinkedHashMap();
}
public void init(final IWorkbench workbench) {}
@Override
protected void createFieldEditors() {
Composite parent = this.getFieldEditorParent();
GridDataFactory factory = GridDataFactory.fillDefaults().grab(true, false).span(2, 1);
Group nodeGroup = Groups.create(parent, "Node", factory.create());
this.addField(newStringFieldEditor(nodeName.getKey(), "Name:", stringParser(), nodeGroup));
Groups.updateMargins(nodeGroup);
Group clusterGroup = Groups.create(parent, "Cluster", factory.create());
this.addField(newStringFieldEditor(clusterName.getKey(), "Name:", stringParser(), clusterGroup));
Groups.updateMargins(clusterGroup);
Group clientGroup = Groups.create(parent, "Client", factory.create());
this.addField(newStringFieldEditor(clientTransportAddresses.getKey(), "Addresses:", arrayListParser(inetSocketAddressParser(), ",", "", ""), clientGroup));
this.addField(newStringFieldEditor(clientTransportNodesSamplerInterval.getKey(), "Nodes sampler interval:", timeParser(), clientGroup));
this.addField(newStringFieldEditor(clientTransportPingTimeout.getKey(), "Ping timeout:", timeParser(), clientGroup));
this.addField(newBooleanFieldEditor(clientTransportIgnoreClusterName.getKey(), "Ignore cluster name validation:", clientGroup));
Groups.updateMargins(clientGroup);
Group transportGroup = Groups.create(parent, "Transport", factory.create());
this.addField(newStringFieldEditor(transportTcpConnectTimeout.getKey(), "Connect timeout:", timeParser(), transportGroup));
this.addField(newBooleanFieldEditor(transportTcpCompress.getKey(), "Compress:", transportGroup));
Groups.updateMargins(transportGroup);
Group networkGroup = Groups.create(parent, "Network", factory.create());
this.addField(newBooleanFieldEditor(networkTcpNoDelay.getKey(), "No delay:", networkGroup));
this.addField(newBooleanFieldEditor(networkTcpKeepAlive.getKey(), "Keep alive:", networkGroup));
this.addField(newBooleanFieldEditor(networkTcpReuseAddress.getKey(), "Reuse address:", networkGroup));
this.addField(newStringFieldEditor(networkTcpSendBufferSize.getKey(), "Send buffer size:", byteSizeParser(), networkGroup));
this.addField(newStringFieldEditor(networkTcpReceiveBufferSize.getKey(), "Receive buffer size:", byteSizeParser(), networkGroup));
Groups.updateMargins(networkGroup);
Group pathGroup = Groups.create(parent, "Path", factory.create());
this.addField(newStringFieldEditor(pathLogs.getKey(), "Logs path:", pathParser(), pathGroup));
this.addField(newStringFieldEditor(pathWork.getKey(), "Work path:", pathParser(), pathGroup));
Groups.updateMargins(pathGroup);
Group notificationGroup = Groups.create(parent, "Notification", factory.create());
this.addField(new BooleanFieldEditor(displayErrors.getKey(), "Display error dialog on service failure", notificationGroup));
Groups.updateMargins(notificationGroup);
Group logGroup = Groups.create(parent, "Log", factory.create());
this.addField(new BooleanFieldEditor(logNotices.getKey(), "Write notices to Workspace Log on proxy management", logGroup));
this.addField(new BooleanFieldEditor(logErrors.getKey(), "Write errors to Error Log on service failure", logGroup));
Groups.updateMargins(logGroup);
}
@Override
protected void contributeButtons(final Composite parent) {
Buttons.createCentering(parent, "Settings", GridData.HORIZONTAL_ALIGN_FILL, new WidgetListener() {
public void handleEvent(final Event event) {
ElasticsearchOptions options = getElasticsearchOptions();
String title = "Elasticsearch Proxy Settings";
String message = "Elasticsearch settings to create a proxy:";
String text = ElasticsearchHandler.formatSettings(options.toSettings());
MessageDialogWithTextArea.open(MessageDialog.NONE, getShell(), title, message, text, SWT.NONE);
}
});
Buttons.createCentering(parent, "Status", GridData.HORIZONTAL_ALIGN_FILL, new WidgetListener() {
public void handleEvent(final Event event) {
requestClusterState();
}
});
((GridLayout) parent.getLayout()).numColumns += 2;
}
static class StringOptionFieldEditor<T> extends StringFieldEditor {
protected OptionParser<? extends T> parser;
public StringOptionFieldEditor(final String name, final String label, final OptionParser<? extends T> parser, final Composite parent) {
super(name, label, parent);
this.parser = checkNotNull(parser);
}
@Override
protected boolean doCheckState() {
String value = this.getStringValue();
if (this.isEmptyStringAllowed() && isNullOrEmpty(value)) {
return true;
}
try {
return this.parser.parse(value) != null;
} catch (RuntimeException e) {
return false;
}
}
}
static StringFieldEditor newStringFieldEditor(final String name, final String label, final OptionParser<?> parser, final Composite parent) {
StringFieldEditor editor = new StringOptionFieldEditor<>(name, label, parser, parent);
editor.setValidateStrategy(StringFieldEditor.VALIDATE_ON_KEY_STROKE);
return editor;
}
static BooleanFieldEditor newBooleanFieldEditor(final String name, final String label, final Composite parent) {
return new BooleanFieldEditor(name, label, BooleanFieldEditor.SEPARATE_LABEL, parent);
}
@Override
protected void addField(final FieldEditor editor) {
this.editors.put(editor.getPreferenceName(), editor);
super.addField(editor);
}
@Override
protected IPreferenceStore doGetPreferenceStore() {
return ElasticsearchPreferences.getShared().getPreferenceStore();
}
boolean requestClusterState() {
try {
ElasticsearchOptions options = this.getElasticsearchOptions();
ElasticsearchProxy proxy = ElasticsearchHandler.createProxy(options);
ClusterStateResponse state = ElasticsearchHandler.requestClusterState(proxy);
ClusterStatsResponse stats = ElasticsearchHandler.requestClusterStats(proxy);
String desiredCluster = ((StringFieldEditor) this.editors.get(clusterName.getKey())).getStringValue();
String receivedCluster = stats.getClusterNameAsString();
String message;
if (!desiredCluster.equals(receivedCluster)) {
message = "Connected to cluster " + desiredCluster + " instead of " + receivedCluster;
message += ", consider connecting to the cluster with correct name.";
MessageDialog.openWarning(this.getShell(), "Elasticsearch Warning", message);
}
message = "Connected to cluster " + receivedCluster;
message += " with " + toLowerCase(stats.getStatus()) + " status.";
String text = format("\"state\" : %s%n%n\"stats\" : %s", state.getState(), stats);
MessageDialogWithTextArea.openInformation(this.getShell(), "Elasticsearch Status", message, text);
return true;
} catch (Exception failure) {
String title, message;
if (failure instanceof ElasticsearchException) {
ElasticsearchException exception = (ElasticsearchException) failure;
title = "Elasticsearch Error";
message = format("%s%n%n%s", exception.status(), exception.getDetailedMessage());
} else {
title = "Unknown Error";
message = failure.getMessage();
}
MessageDialog.openError(this.getShell(), title, message);
return false;
}
}
ElasticsearchOptions getElasticsearchOptions() {
Map<String, Object> options = newLinkedHashMap();
for (FieldEditor editor: this.editors.values()) {
String key = editor.getPreferenceName();
if (editor instanceof BooleanFieldEditor) {
options.put(key, ((BooleanFieldEditor) editor).getBooleanValue());
} else if (editor instanceof StringFieldEditor) {
options.put(key, ((StringFieldEditor) editor).getStringValue());
} else {
throw new IllegalStateException();
}
}
return ElasticsearchOptions.View.of(MapOptions.view(options));
}
}