package sk.stuba.fiit.perconik.activity.listeners;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.indices.IndexMissingException;
import com.gratex.perconik.uaca.SharedUacaProxy;
import com.gratex.perconik.uaca.data.UacaEvent;
import com.gratex.perconik.uaca.preferences.UacaOptions;
import sk.stuba.fiit.perconik.activity.data.core.StandardCoreProbe;
import sk.stuba.fiit.perconik.activity.data.platform.StandardPlatformProbe;
import sk.stuba.fiit.perconik.activity.data.process.StandardProcessProbe;
import sk.stuba.fiit.perconik.activity.data.system.StandardSystemProbe;
import sk.stuba.fiit.perconik.activity.listeners.RegularListener.RegularConfiguration.Builder;
import sk.stuba.fiit.perconik.activity.plugin.Activator;
import sk.stuba.fiit.perconik.activity.preferences.ActivityPreferences;
import sk.stuba.fiit.perconik.activity.probes.Probe;
import sk.stuba.fiit.perconik.core.Registrables;
import sk.stuba.fiit.perconik.core.preferences.ListenerPreferences;
import sk.stuba.fiit.perconik.data.AnyStructuredData;
import sk.stuba.fiit.perconik.data.bind.Defaults;
import sk.stuba.fiit.perconik.data.bind.Mapper;
import sk.stuba.fiit.perconik.data.bind.Writer;
import sk.stuba.fiit.perconik.data.content.AnyStructuredContent;
import sk.stuba.fiit.perconik.data.content.Content;
import sk.stuba.fiit.perconik.data.events.Event;
import sk.stuba.fiit.perconik.data.store.Store;
import sk.stuba.fiit.perconik.eclipse.core.runtime.ForwardingPluginConsole;
import sk.stuba.fiit.perconik.eclipse.core.runtime.PluginConsole;
import sk.stuba.fiit.perconik.eclipse.core.runtime.PluginConsoles;
import sk.stuba.fiit.perconik.elasticsearch.SharedElasticsearchProxy;
import sk.stuba.fiit.perconik.elasticsearch.preferences.ElasticsearchOptions;
import sk.stuba.fiit.perconik.utilities.SmartStringBuilder;
import sk.stuba.fiit.perconik.utilities.concurrent.TimeUnits;
import sk.stuba.fiit.perconik.utilities.concurrent.TimeValue;
import sk.stuba.fiit.perconik.utilities.configuration.Configurables;
import sk.stuba.fiit.perconik.utilities.configuration.OptionAccessor;
import sk.stuba.fiit.perconik.utilities.configuration.Options;
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.ObjectArrays.concat;
import static com.gratex.perconik.uaca.GenericUacaProxyConstants.GENERIC_EVENT_PATH;
import static sk.stuba.fiit.perconik.activity.listeners.AbstractListener.RegistrationHook.POST_REGISTER;
import static sk.stuba.fiit.perconik.activity.listeners.AbstractListener.RegistrationHook.POST_UNREGISTER;
import static sk.stuba.fiit.perconik.activity.listeners.AbstractListener.RegistrationHook.PRE_REGISTER;
import static sk.stuba.fiit.perconik.activity.listeners.AbstractListener.RegistrationHook.PRE_UNREGISTER;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.LoggingOptions.Schema.logDebug;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.LoggingOptions.Schema.logErrors;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.LoggingOptions.Schema.logEvents;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.LoggingOptions.Schema.logNotices;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.PersistenceOptions.Schema.persistenceElasticsearch;
import static sk.stuba.fiit.perconik.activity.listeners.ActivityListener.PersistenceOptions.Schema.persistenceUaca;
import static sk.stuba.fiit.perconik.activity.listeners.RegularListener.RegularConfiguration.builder;
import static sk.stuba.fiit.perconik.activity.plugin.Activator.PLUGIN_ID;
import static sk.stuba.fiit.perconik.activity.serializers.References.referenceStrings;
import static sk.stuba.fiit.perconik.data.content.StructuredContent.separator;
import static sk.stuba.fiit.perconik.data.content.StructuredContents.key;
import static sk.stuba.fiit.perconik.data.content.StructuredContents.sequence;
import static sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayExecutor.defaultSynchronous;
import static sk.stuba.fiit.perconik.preferences.AbstractPreferences.Keys.join;
import static sk.stuba.fiit.perconik.utilities.MorePreconditions.checkNotNullAsState;
import static sk.stuba.fiit.perconik.utilities.MorePreconditions.checkNotNullOrEmpty;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.toDefaultString;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.toLowerCaseFunction;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.trimLeading;
import static sk.stuba.fiit.perconik.utilities.MoreThrowables.initializeSuppressor;
import static sk.stuba.fiit.perconik.utilities.concurrent.PlatformExecutors.defaultPoolSizeScalingFactor;
import static sk.stuba.fiit.perconik.utilities.concurrent.PlatformExecutors.newLimitedThreadPool;
import static sk.stuba.fiit.perconik.utilities.configuration.Configurables.emptyOptions;
import static sk.stuba.fiit.perconik.utilities.configuration.Configurables.option;
import static sk.stuba.fiit.perconik.utilities.configuration.OptionParsers.booleanParser;
/**
* TODO
*
* @author Pavol Zbell
* @since 1.0
*/
public abstract class ActivityListener extends RegularListener<ActivityListener> {
static final float sharedExecutorPoolSizeScalingFactor = defaultPoolSizeScalingFactor();
static final float probeExecutorPoolSizeScalingFactor = 0.5f;
protected static final String qualifier = join(PLUGIN_ID, "preferences");
private static final Builder<ActivityListener, ActivityListener> sharedBuilder;
static {
sharedBuilder = builder();
sharedBuilder.contextType(ActivityListener.class);
sharedBuilder.listenerType(ActivityListener.class);
sharedBuilder.optionsLoader(OptionsSupplier.instance);
sharedBuilder.pluginConsole(PluginConsoles.create(Activator.defaultInstance()));
sharedBuilder.diplayExecutor(defaultSynchronous());
sharedBuilder.sharedExecutor(newLimitedThreadPool(sharedExecutorPoolSizeScalingFactor));
sharedBuilder.persistenceStore(ProxySupplier.instance);
sharedBuilder.sendFailureHandler(ProxySendFailureHandler.instance);
Map<String, Probe<?>> probes = newHashMap();
probes.put(key("monitor", "core"), new StandardCoreProbe());
//probes.put(key("monitor", "management"), new StandardManagementProbe());// TODO
probes.put(key("monitor", "platform"), new StandardPlatformProbe());
probes.put(key("monitor", "process"), new StandardProcessProbe());
probes.put(key("monitor", "system"), new StandardSystemProbe());
sharedBuilder.probeMappings(probes);
sharedBuilder.probeFilter(ProbingOptionsFilterSupplierFunction.instance);
sharedBuilder.probeExecutor(newLimitedThreadPool(probeExecutorPoolSizeScalingFactor));
sharedBuilder.registerFailureHandler(LoggingRegisterFailureHandler.instance);
sharedBuilder.disposalHook(ProxyDisposalHook.instance);
}
/**
* Underlying listener specific console, output does not depend on any options.
* <p>
* Note that underlying {@code pluginConsole} provides not-listener-specific
* access to respective plug-in console and therefore should not be used directly.
*/
protected final Console console;
/**
* Underlying listener debug log, output depends on the {@code log.debug} option.
* <p>
* Note that underlying {@code pluginConsole} provides not-listener-specific
* access to respective plug-in console and therefore should not be used directly.
*/
protected final Log log;
/**
* Constructor for use by subclasses.
*/
protected ActivityListener() {
super(newConfiguration());
this.console = new Console(this);
this.log = new Log(this);
}
static final Configuration<ActivityListener, ActivityListener> newConfiguration() {
return sharedBuilder.build();
}
public interface ProbingOptions extends Options {
public static final class Schema {
public static final OptionAccessor<Boolean> monitorCore = option(booleanParser(), join(qualifier, "monitor", "core"), false);
// TODO public static final OptionAccessor<Boolean> monitorManagement = option(booleanParser(), join(qualifier, "monitor", "management"), false);
public static final OptionAccessor<Boolean> monitorPlatform = option(booleanParser(), join(qualifier, "monitor", "platform"), false);
public static final OptionAccessor<Boolean> monitorProcess = option(booleanParser(), join(qualifier, "monitor", "process"), false);
public static final OptionAccessor<Boolean> monitorSystem = option(booleanParser(), join(qualifier, "monitor", "system"), false);
public static final OptionAccessor<Boolean> listenerInstance = option(booleanParser(), join(qualifier, "listener", "instance"), true);
public static final OptionAccessor<Boolean> listenerConfiguration = option(booleanParser(), join(qualifier, "listener", "configuration"), false);
public static final OptionAccessor<Boolean> listenerRegistration = option(booleanParser(), join(qualifier, "listener", "registration"), false);
public static final OptionAccessor<Boolean> listenerOptions = option(booleanParser(), join(qualifier, "listener", "options"), true);
public static final OptionAccessor<Boolean> listenerStatistics = option(booleanParser(), join(qualifier, "listener", "statistics"), true);
static final ImmutableMap<String, OptionAccessor<Boolean>> probeKeyToOptionAccessor;
static {
Map<String, OptionAccessor<Boolean>> map = newHashMap();
for (OptionAccessor<Boolean> accessor: Configurables.accessors(Schema.class, Boolean.class)) {
String key = accessor.getKey();
checkState(!map.containsKey(key), "%s: internal probe key conflict detected on %s", Schema.class, key);
map.put(key, accessor);
}
probeKeyToOptionAccessor = ImmutableMap.copyOf(map);
}
private Schema() {}
}
}
public interface PersistenceOptions extends Options {
public static final class Schema {
public static final OptionAccessor<Boolean> persistenceElasticsearch = option(booleanParser(), join(qualifier, "persistence", "elasticsearch"), false);
public static final OptionAccessor<Boolean> persistenceUaca = option(booleanParser(), join(qualifier, "persistence", "uaca"), true);
private Schema() {}
}
}
public interface LoggingOptions extends Options {
public static final class Schema {
public static final OptionAccessor<Boolean> logDebug = option(booleanParser(), join(qualifier, "log", "debug"), false);
public static final OptionAccessor<Boolean> logEvents = option(booleanParser(), join(qualifier, "log", "events"), false);
public static final OptionAccessor<Boolean> logNotices = option(booleanParser(), join(qualifier, "log", "notices"), false);
public static final OptionAccessor<Boolean> logErrors = option(booleanParser(), join(qualifier, "log", "errors"), true);
private Schema() {}
}
}
private static final class OptionsLoader extends AbstractOptionsLoader<ActivityListener> {
protected OptionsLoader(final ActivityListener listener) {
super(listener);
}
@Override
protected ActivityPreferences defaultPreferences() {
return ActivityPreferences.getShared();
}
@Override
protected ListenerPreferences customPreferences() {
return ListenerPreferences.getShared();
}
@Override
protected Options adjustDefaultOptions(final ActivityListener listener) {
return listener.adjustDefaultOptions();
}
@Override
protected Options adjustCustomOptions(final ActivityListener listener) {
return emptyOptions();
}
}
private enum OptionsSupplier implements Function<ActivityListener, OptionsLoader> {
instance;
public OptionsLoader apply(@Nonnull final ActivityListener listener) {
return new OptionsLoader(listener);
}
}
// TODO refactor somehow
@SuppressWarnings("static-method")
protected Options adjustDefaultOptions() {
return emptyOptions();
}
protected static final class Console extends ForwardingPluginConsole {
private final ActivityListener listener;
private final PluginConsole console;
Console(final ActivityListener listener) {
this.listener = checkNotNull(listener);
this.console = checkNotNull(listener.pluginConsole);
}
@Override
protected PluginConsole delegate() {
return this.console;
}
@Override
public void notice(@Nullable final String message) {
super.notice("%s: %s", this.listener, message);
}
@Override
public void notice(final String format, final Object ... args) {
super.notice("%s: " + format, concat(this.listener, args));
}
@Override
public void warning(@Nullable final String message) {
super.warning("%s: %s", this.listener, message);
}
@Override
public void warning(final String format, final Object ... args) {
super.warning("%s: " + format, concat(this.listener, args));
}
@Override
public void error(@Nullable final String message) {
super.error("%s: %s", this.listener, message);
}
@Override
public void error(final String format, final Object ... args) {
super.error("%s: " + format, concat(this.listener, args));
}
@Override
public void error(final Throwable failure, @Nullable final String message) {
super.error(failure, "%s: %s", this.listener, message);
}
@Override
public void error(final Throwable failure, final String format, final Object ... args) {
super.error(failure, "%s: " + format, concat(this.listener, args));
}
}
private enum LoggingRegisterFailureHandler implements RegisterFailureHandler<ActivityListener> {
instance;
static void report(final ActivityListener listener, final RegistrationHook hook, final Runnable task, final Exception failure) {
if (logErrors.getValue(listener.effectiveOptions())) {
listener.console.error(failure, "unexpected failure while executing %s as %s hook", task, hook);
}
}
public void preRegisterFailure(final ActivityListener listener, final Runnable task, final Exception failure) {
report(listener, PRE_REGISTER, task, failure);
}
public void postRegisterFailure(final ActivityListener listener, final Runnable task, final Exception failure) {
report(listener, POST_REGISTER, task, failure);
}
public void preUnregisterFailure(final ActivityListener listener, final Runnable task, final Exception failure) {
report(listener, PRE_UNREGISTER, task, failure);
}
public void postUnregisterFailure(final ActivityListener listener, final Runnable task, final Exception failure) {
report(listener, POST_UNREGISTER, task, failure);
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
private static final class ElasticsearchProxy extends SharedElasticsearchProxy implements Store<ActivityListener, Event> {
ElasticsearchProxy(final ElasticsearchOptions options) {
super(options);
}
private enum IndexSupplier implements Function<Event, String> {
instance;
public String apply(@Nonnull final Event data) {
long timestamp = data.getTimestamp();
return "perconik-events-" + new SimpleDateFormat("yyyyMMdd").format(new Date(timestamp));
}
}
private enum TypeSupplier implements Function<Event, String> {
instance;
public String apply(@Nonnull final Event data) {
return "event";
}
}
public Content load(final ActivityListener listener, final String path, @Nonnull final Event data) {
throw new UnsupportedOperationException();
}
public void save(final ActivityListener listener, final String path, @Nonnull final Event data) {
// in order for index creation and type mapping to work properly,
// action.auto_create_index setting must be disabled on every node
final String index = IndexSupplier.instance.apply(data);
final String type = TypeSupplier.instance.apply(data);
final Map<String, Object> source = source(path, data);
this.execute(new Task<Void>() {
public Void perform(final TransportClient client) {
index(listener, client, index, type, source);
return null;
}
});
}
void index(final ActivityListener listener, final TransportClient client, final String index, final String type, final Map<String, Object> source) {
IndexRequest request = client.prepareIndex(index, type).setSource(source).request();
// TODO refactor this to use index templates
try {
IndexResponse response = client.index(request).actionGet();
checkState(response.isCreated(), "%s -> event %s not created", listener, source.get("action"));
} catch (IndexMissingException failure) {
if (ElasticsearchOptions.Schema.logNotices.getValue(this.options)) {
reportMessage(format("%s -> index %s not exists, create index and reindex event", listener, index));
}
this.create(listener, client, index, IndexSource.get(listener, client, index, type));
IndexResponse response = client.index(request).actionGet();
checkState(response.isCreated(), "%s -> event %s not created", listener, source.get("action"));
}
}
void create(final ActivityListener listener, final TransportClient client, final String index, final Map<String, Object> source) {
try {
if (ElasticsearchOptions.Schema.logNotices.getValue(this.options)) {
String raw = AnyStructuredData.of(source).toString(true);
this.reportMessage(format("%s -> creating index %s from source: %s", listener, index, raw));
}
CreateIndexResponse response = client.admin().indices().prepareCreate(index).setSource(source).get();
checkState(response.isAcknowledged(), "%s -> index %s not acknowledged", listener, index);
} catch (IndexAlreadyExistsException failure) {
if (ElasticsearchOptions.Schema.logNotices.getValue(this.options)) {
this.reportMessage(format("%s -> index %s already exists, reindex event", listener, index));
}
}
}
private static final class IndexSource {
private static final String VERSION = "0.0.2.alpha";
private IndexSource() {}
private static Map<String, Object> settings() {
AnyStructuredContent settings = new AnyStructuredData();
settings.put(key("number_of_shards"), 1);
settings.put(key("number_of_replicas"), 0);
settings.put(key("analysis"), analysis());
return settings.toMap();
}
private static Map<String, Object> analysis() {
AnyStructuredContent analysis = new AnyStructuredData();
analysis.put(key("analyzer", "action_analyzer", "type"), "custom");
analysis.put(key("analyzer", "action_analyzer", "tokenizer"), "action_tokenizer");
analysis.put(key("analyzer", "path_analyzer", "type"), "custom");
analysis.put(key("analyzer", "path_analyzer", "tokenizer"), "path_tokenizer");
analysis.put(key("tokenizer", "action_tokenizer", "type"), "PathHierarchy");
analysis.put(key("tokenizer", "action_tokenizer", "delimeter"), '.');
analysis.put(key("tokenizer", "path_tokenizer", "type"), "PathHierarchy");
analysis.put(key("tokenizer", "path_tokenizer", "delimeter"), '/');
return analysis.toMap();
}
private static Map<String, Object> mappings(final ActivityListener listener, final TransportClient client) {
AnyStructuredContent mappings = new AnyStructuredData();
mappings.put(key("_all", "enabled"), true);
mappings.put(key("_uid", "enabled"), false);
mappings.put(key("_source", "enabled"), true);
mappings.put(key("_index", "enabled"), true);
mappings.put(key("_index", "type"), "string");
mappings.put(key("_index", "store"), true);
mappings.put(key("_index", "index"), "not_analyzed");
mappings.put(key("_type", "enabled"), true);
mappings.put(key("_type", "type"), "string");
mappings.put(key("_type", "store"), true);
mappings.put(key("_type", "index"), "not_analyzed");
mappings.put(key("_id", "enabled"), true);
mappings.put(key("_id", "type"), "string");
mappings.put(key("_id", "store"), true);
mappings.put(key("_id", "index"), "not_analyzed");
mappings.put(key("_timestamp", "enabled"), true);
mappings.put(key("_timestamp", "type"), "long");
mappings.put(key("_timestamp", "store"), true);
mappings.put(key("_timestamp", "index"), "no");
mappings.put(key("_meta"), meta(listener, client));
mappings.put(key("dynamic"), false);
mappings.put(key("properties"), properties());
return mappings.toMap();
}
private static Map<String, Object> meta(final ActivityListener listener, final TransportClient client) {
AnyStructuredContent meta = new AnyStructuredData();
Settings settings = client.settings();
meta.put(key("creator", "node", "name"), settings.get("name"));
meta.put(key("creator", "node", "client"), settings.get("node.client"));
meta.put(key("creator", "node", "master"), settings.get("node.master"));
meta.put(key("creator", "node", "data"), settings.get("node.data"));
meta.put(key("creator", "client", "type"), settings.get("client.type"));
meta.put(key("creator", "client", "version"), Version.CURRENT.toString());
meta.put(key("creator", "listener", "class"), listener.getClass().getName());
meta.put(key("creator", "listener", "version"), Registrables.getVersion(listener.getClass()));
meta.put(key("version"), VERSION);
meta.put(key("tagline"), "You Know, for Research");
return meta.toMap();
}
private static Map<String, Object> properties() {
AnyStructuredContent properties = new AnyStructuredData();
properties.put(key("path", "type"), "string");
properties.put(key("path", "store"), true);
properties.put(key("path", "index"), "analyzed");
properties.put(key("path", "analyzer"), "path_analyzer");
properties.put(key("action", "type"), "string");
properties.put(key("action", "store"), true);
properties.put(key("action", "index"), "analyzed");
properties.put(key("action", "analyzer"), "action_analyzer");
properties.put(key("time", "type"), "date");
properties.put(key("time", "store"), true);
properties.put(key("time", "index"), "not_analyzed");
properties.put(key("time", "format"), Defaults.TIME_PATTERN);
properties.put(key("timestamp", "type"), "long");
properties.put(key("timestamp", "store"), true);
properties.put(key("timestamp", "index"), "no");
return properties.toMap();
}
static Map<String, Object> get(final ActivityListener listener, final TransportClient client, final String index, final String type) {
assert !isNullOrEmpty(index);
assert !isNullOrEmpty(type);
AnyStructuredContent source = new AnyStructuredData();
source.put(key("settings"), settings());
source.put(key("mappings", type), mappings(listener, client));
return source.toMap();
}
}
private static Map<String, Object> source(final String path, final Event data) {
final Map<String, Object> source = data.toMap();
source.put("path", path);
return source;
}
}
private static final class UacaProxy extends SharedUacaProxy implements Store<ActivityListener, Object> {
UacaProxy(final UacaOptions options) {
super(options);
}
public Content load(final ActivityListener listener, final String path, @Nullable final Object request) {
throw new UnsupportedOperationException();
}
public void save(final ActivityListener listener, final String path, @Nullable final Object resource) {
this.send(GENERIC_EVENT_PATH, UacaEvent.of(path, resource));
}
}
private static final class ProxyPersistenceStore implements PersistenceStore<ActivityListener> {
private final ProxyHolder<ElasticsearchProxy> elasticsearch;
private final ProxyHolder<UacaProxy> uaca;
ProxyPersistenceStore(final ProxyHolder<ElasticsearchProxy> elasticsearch, final ProxyHolder<UacaProxy> uaca) {
this.elasticsearch = checkNotNull(elasticsearch);
this.uaca = checkNotNull(uaca);
}
private static void report(final ActivityListener listener, final String path, final Event data) {
try {
Map<?, ?> raw = Mapper.getShared().convertValue(data, Mapper.getMapType());
String serial = Writer.getPretty().writeValueAsString(raw);
listener.console.notice(format("%s%n%s", path, serial));
} catch (JsonProcessingException | RuntimeException failure) {
listener.console.error(failure, "unable to format %s", toDefaultString(data));
}
}
private static void save(final Store<? super ActivityListener, ? super Event> store, final ActivityListener listener, final String path, final Event data, final List<Exception> failures) {
try {
store.save(listener, path, data);
} catch (Exception failure) {
failures.add(failure);
}
}
private static void close(final Optional<? extends Store<? super ActivityListener, ? super Event>> store, final List<Exception> failures) {
if (!store.isPresent()) {
return;
}
try {
store.get().close();
} catch (Exception failure) {
failures.add(failure);
}
}
private static void handle(final List<Exception> failures) throws Exception {
if (!failures.isEmpty()) {
throw initializeSuppressor(new RuntimeException(), failures);
}
}
public void persist(final ActivityListener listener, final String path, final Event data) throws Exception {
Options options = listener.effectiveOptions();
if (logEvents.getValue(options)) {
report(listener, path, data);
}
List<Exception> failures = newLinkedList();
if (persistenceElasticsearch.getValue(options)) {
save(this.elasticsearch.openOrReuse(), listener, path, data, failures);
}
if (persistenceUaca.getValue(options)) {
save(this.uaca.openOrReuse(), listener, path, data, failures);
}
handle(failures);
}
void reload(final ActivityListener listener) {
if (logNotices.getValue(listener.effectiveOptions())) {
listener.console.notice("updating %s", ElasticsearchProxy.class.getName());
}
Optional<ElasticsearchProxy> elasticsearch = this.elasticsearch.tryToReuse();
if (elasticsearch.isPresent()) {
elasticsearch.get().update();
}
}
public void close() throws Exception {
List<Exception> failures = newLinkedList();
close(this.elasticsearch.tryToReuse(), failures);
close(this.uaca.tryToReuse(), failures);
handle(failures);
}
@Override
public String toString() {
return toStringHelper(this).add("elasticsearch", this.elasticsearch).add("uaca", this.uaca).toString();
}
}
private static abstract class ProxyHolder<T extends Store<? super ActivityListener, ?>> {
private volatile boolean opened;
private T store;
ProxyHolder() {}
abstract T open();
final T openOrReuse() {
if (!this.opened) {
synchronized (this) {
if (!this.opened) {
T store = open();
this.store = store;
this.opened = true;
return store;
}
}
}
return this.store;
}
final Optional<T> tryToReuse() {
if (!this.opened) {
synchronized (this) {
if (!this.opened) {
return absent();
}
}
}
return fromNullable(this.store);
}
}
private enum ProxySupplier implements Function<ActivityListener, PersistenceStore<ActivityListener>> {
instance;
public PersistenceStore<ActivityListener> apply(final ActivityListener listener) {
ProxyHolder<ElasticsearchProxy> elasticsearch = new ProxyHolder<ElasticsearchProxy>() {
@Override
ElasticsearchProxy open() {
try {
return new ElasticsearchProxy(listener.getElasticsearchOptions());
} catch (Exception failure) {
throw handleConstructFailure(listener, ElasticsearchProxy.class, failure);
}
}
};
ProxyHolder<UacaProxy> uaca = new ProxyHolder<UacaProxy>() {
@Override
UacaProxy open() {
try {
return new UacaProxy(listener.getUacaOptions());
} catch (Exception failure) {
throw handleConstructFailure(listener, UacaProxy.class, failure);
}
}
};
return new ProxyPersistenceStore(elasticsearch, uaca);
}
static RuntimeException handleConstructFailure(final ActivityListener listener, final Class<? extends Store<?, ?>> implementation, final Exception failure) {
if (logErrors.getValue(listener.effectiveOptions())) {
listener.console.error(failure, "unable to construct %s", implementation.getName());
}
throw new IllegalStateException(format("%s: unexpected failure while opening %s", listener, implementation.getName()), failure);
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
private enum ProxySendFailureHandler implements SendFailureHandler<ActivityListener> {
instance;
public void handleSendFailure(final ActivityListener listener, final String path, final Event data, final Exception failure) {
if (logErrors.getValue(listener.effectiveOptions())) {
listener.console.error(failure, "unable to send %s to %s using %s", toDefaultString(data), path, listener.persistenceStore);
}
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
private enum ProxyDisposalHook implements DisposalHook<ActivityListener> {
instance;
public void onDispose(final ActivityListener listener) {
try {
listener.persistenceStore.close();
} catch (Exception failure) {
handleDisposeFailure(listener, failure);
}
}
static void handleDisposeFailure(final ActivityListener listener, final Exception failure) {
if (logErrors.getValue(listener.effectiveOptions())) {
listener.console.error(failure, "unable to dispose %s", listener.persistenceStore);
}
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
@Override
protected Map<String, InternalProbe<?>> internalProbeMappings() {
ImmutableMap.Builder<String, InternalProbe<?>> builder = ImmutableMap.builder();
builder.put(key("listener", "instance"), new RegularInstanceProbe());
builder.put(key("listener", "configuration"), new RegularConfigurationProbe());
builder.put(key("listener", "registration"), new RegularRegistrationProbe());
builder.put(key("listener", "options"), new RegularOptionsProbe());
builder.put(key("listener", "statistics"), new RegularStatisticsProbe());
return builder.build();
}
private enum ProbingOptionsFilterSupplierFunction implements Function<ActivityListener, Predicate<Entry<String, Probe<?>>>> {
instance;
public Predicate<Entry<String, Probe<?>>> apply(@Nonnull final ActivityListener listener) {
return new ProbingOptionsFilter(listener);
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
private static final class ProbingOptionsFilter implements Predicate<Entry<String, Probe<?>>> {
private final ActivityListener listener;
private final Options options;
private final Map<String, OptionAccessor<Boolean>> accessors;
ProbingOptionsFilter(final ActivityListener listener) {
this.listener = checkNotNull(listener);
this.accessors = checkNotNull(listener.probeKeyToOptionAccessor());
this.options = checkNotNull(listener.effectiveOptions());
}
public boolean apply(@Nonnull final Entry<String, Probe<?>> entry) {
String key = probeKeyToOptionKey(entry.getKey());
OptionAccessor<Boolean> accessor = this.accessors.get(key);
checkNotNullAsState(accessor, "%s: no accessor found for %s option, available accessors %s", this.listener, key, this.accessors);
return accessor.getValue(this.options);
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
protected static String probeKeyToOptionKey(final String key) {
return qualifier + separator + trimLeading(key, internalProbeKeyPrefix + separator);
}
protected Map<String, OptionAccessor<Boolean>> probeKeyToOptionAccessor() {
checkNotNull(this.optionsProvider);
return ProbingOptions.Schema.probeKeyToOptionAccessor;
}
public interface Action {
public String getName();
public String getPath();
}
public static final String actionName(final Object ... components) {
return key(transform(asList(components), toLowerCaseFunction()));
}
public static final String actionPath(final Object ... components) {
StringBuilder builder = new StringBuilder(16 * components.length);
for (Object component: components) {
for (String item: sequence(component.toString())) {
builder.append(item.replace('_', '-').toLowerCase()).append('/');
}
}
return builder.substring(0, builder.length() - 1);
}
protected static abstract class ContinuousEvent<L extends ActivityListener, E> extends RegularListener.ContinuousEvent<L, E> {
protected final String identifier;
protected final Log log;
protected ContinuousEvent(final L listener, final String identifier, final long pause, final long window, final TimeUnit unit) {
super(listener, pause, window, unit);
this.identifier = checkNotNullOrEmpty(identifier);
this.log = this.listener.log;
}
protected ContinuousEvent(final L listener, final String identifier, final TimeValue pause, final TimeValue window) {
this(listener, identifier, pause.durationToMillis(), window.durationToMillis(), MILLISECONDS);
}
protected String formatElapsedTime(final long delta, final long total) {
return format("pause %s, window %s", formatTimeComparison(delta, this.pause), formatTimeComparison(total, this.window));
}
protected String formatTimeComparison(final long value, final long limit) {
return format("%d %s %d%s", value, value < limit ? "<" : ">=", limit, TimeUnits.toString(this.unit));
}
@Override
protected void watchRunningButEventsNotContinouous() {
if (this.log.isEnabled()) {
this.log.print("%s: watch running but %1$s events not continuous -> process", this.identifier);
}
}
@Override
protected void watchNotRunning() {
if (this.log.isEnabled()) {
this.log.print("%s: watch not running -> clear", this.identifier);
}
}
@Override
protected void watchTimeNotElapsed(final long delta) {
if (this.log.isEnabled()) {
this.log.print("%s: %s -> wait", this.identifier, this.formatElapsedTime(delta, this.total()));
}
}
@Override
protected void watchTimeElapsedAndAboutToProcess(final long delta) {
if (this.log.isEnabled()) {
this.log.print("%s: %s -> process", this.identifier, this.formatElapsedTime(delta, this.total()));
}
}
}
// TODO make intern optional, read from this.getOptions()
@SuppressWarnings("static-method")
protected final Event intern(final Event data) {
referenceStrings(data);
return data;
}
protected static final class Log extends ForwardingPluginConsole {
private final Options options;
private final PluginConsole console;
Log(final ActivityListener listener) {
this.options = checkNotNull(listener.effectiveOptions());
this.console = checkNotNull(listener.pluginConsole);
}
@Override
protected PluginConsole delegate() {
return this.isEnabled() ? this.console : PluginConsoles.silent();
}
public static void format(final String content) {
SmartStringBuilder.builder(2 * content.length()).format(valueOf(content));
}
public static void format(final String format, final Object ... args) {
SmartStringBuilder.builder(4 * format.length()).format(format, args);
}
public boolean isEnabled() {
return logDebug.getValue(this.options);
}
}
@Override
protected final void onOptionsReload() {
super.onOptionsReload();
((ProxyPersistenceStore) this.persistenceStore).reload(this);
this.onOptionsReloadHook();
}
protected void onOptionsReloadHook() {}
final ElasticsearchOptions getElasticsearchOptions() {
return ElasticsearchOptions.View.of(this.effectiveOptions());
}
final UacaOptions getUacaOptions() {
return UacaOptions.View.of(this.effectiveOptions());
}
}