All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.trigger.TriggersModule Maven / Gradle / Ivy

/*
 * Copyright (C) 2015-2018 SoftIndex LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.datakernel.trigger;

import io.datakernel.common.Initializable;
import io.datakernel.common.Initializer;
import io.datakernel.di.annotation.Optional;
import io.datakernel.di.annotation.Provides;
import io.datakernel.di.annotation.ProvidesIntoSet;
import io.datakernel.di.core.Injector;
import io.datakernel.di.core.Key;
import io.datakernel.di.module.AbstractModule;
import io.datakernel.launcher.LauncherService;
import io.datakernel.trigger.jmx.KeyWithWorkerData;
import io.datakernel.trigger.util.Utils;
import io.datakernel.worker.WorkerPool;
import io.datakernel.worker.WorkerPools;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;

import static io.datakernel.trigger.util.Utils.prettyPrintSimpleKeyName;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.concurrent.CompletableFuture.completedFuture;

@SuppressWarnings("unused")
public final class TriggersModule extends AbstractModule implements TriggersModuleSettings, Initializable {
	private Function, String> keyToString = Utils::prettyPrintSimpleKeyName;

	private final Map, Set>> classSettings = new LinkedHashMap<>();
	private final Map, Set>> keySettings = new LinkedHashMap<>();

	private static final class TriggerConfig {
		private final Severity severity;
		private final String name;
		private final Function triggerFunction;

		TriggerConfig(Severity severity, String name,
				Function triggerFunction) {
			this.severity = severity;
			this.name = name;
			this.triggerFunction = triggerFunction;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;
			TriggerConfig that = (TriggerConfig) o;
			return severity == that.severity &&
					Objects.equals(name, that.name);
		}

		@Override
		public int hashCode() {
			return Objects.hash(severity, name);
		}
	}

	private static final class TriggerRegistryRecord {
		private final Severity severity;
		private final String name;
		private final Supplier triggerFunction;

		private TriggerRegistryRecord(Severity severity, String name, Supplier triggerFunction) {
			this.severity = severity;
			this.name = name;
			this.triggerFunction = triggerFunction;
		}
	}

	private TriggersModule() {
	}

	public static TriggersModule create() {
		return new TriggersModule();
	}

	@Override
	public TriggersModule withNaming(Function, String> keyToString) {
		this.keyToString = keyToString;
		return this;
	}

	@Override
	public  TriggersModule with(Class type, Severity severity, String name, Function triggerFunction) {
		Set> triggerConfigs = classSettings.computeIfAbsent(type, $ -> new LinkedHashSet<>());

		if (!triggerConfigs.add(new TriggerConfig<>(severity, name, triggerFunction))) {
			throw new IllegalArgumentException("Cannot assign duplicate trigger");
		}

		return this;
	}

	@Override
	public  TriggersModule with(Key key, Severity severity, String name, Function triggerFunction) {
		Set> triggerConfigs = keySettings.computeIfAbsent(key, $ -> new LinkedHashSet<>());

		if (!triggerConfigs.add(new TriggerConfig<>(severity, name, triggerFunction))) {
			throw new IllegalArgumentException("Cannot assign duplicate trigger");
		}

		return this;
	}

	@Provides
	Triggers triggers() {
		return Triggers.create();
	}

	@ProvidesIntoSet
	LauncherService service(Injector injector, Triggers triggers, @Optional Set> initializers) {
		if (initializers != null) {
			for (Initializer initializer : initializers) {
				initializer.accept(this);
			}
		}
		return new LauncherService() {
			@Override
			public CompletableFuture start() {
				doStart(injector, triggers);
				return completedFuture(null);
			}

			@Override
			public CompletableFuture stop() {
				return completedFuture(null);
			}
		};
	}

	@SuppressWarnings("unchecked")
	private void doStart(Injector injector, Triggers triggers) {
		Map> triggersMap = new LinkedHashMap<>();

		// register singletons
		for (Map.Entry, Object> entry : injector.peekInstances().entrySet()) {
			Key key = (Key) entry.getKey();
			Object instance = entry.getValue();
			if (instance == null) continue;
			KeyWithWorkerData internalKey = new KeyWithWorkerData(key);

			scanHasTriggers(triggersMap, internalKey, instance);
			scanClassSettings(triggersMap, internalKey, instance);
			scanKeySettings(triggersMap, internalKey, instance);
		}

		// register workers
		WorkerPools workerPools = injector.peekInstance(WorkerPools.class);
		if (workerPools != null) {
			for (WorkerPool workerPool : workerPools.getWorkerPools()) {
				for (Map.Entry, WorkerPool.Instances> entry : workerPool.peekInstances().entrySet()) {
					Key key = entry.getKey();
					WorkerPool.Instances workerInstances = entry.getValue();
					for (int i = 0; i < workerInstances.size(); i++) {
						Object workerInstance = workerInstances.get(i);
						KeyWithWorkerData k = new KeyWithWorkerData(key, workerPool, i);

						scanHasTriggers(triggersMap, k, workerInstance);
						scanClassSettings(triggersMap, k, workerInstance);
						scanKeySettings(triggersMap, k, workerInstance);
					}
				}

			}
		}

		for (KeyWithWorkerData keyWithWorkerData : triggersMap.keySet()) {
			for (TriggerRegistryRecord registryRecord : triggersMap.getOrDefault(keyWithWorkerData, emptyList())) {
				triggers.addTrigger(registryRecord.severity, prettyPrintSimpleKeyName(keyWithWorkerData.getKey()), registryRecord.name, registryRecord.triggerFunction);
			}
		}
	}

	private void scanHasTriggers(Map> triggers, KeyWithWorkerData internalKey, Object instance) {
		if (instance instanceof HasTriggers) {
			((HasTriggers) instance).registerTriggers(new TriggerRegistry() {
				@Override
				public Key getComponentKey() {
					return internalKey.getKey();
				}

				@Override
				public String getComponentName() {
					return keyToString.apply(internalKey.getKey());
				}

				@Override
				public void add(Severity severity, String name, Supplier triggerFunction) {
					triggers.computeIfAbsent(internalKey, $ -> new ArrayList<>()).add(new TriggerRegistryRecord(severity, name, triggerFunction));
				}
			});
		}
	}

	@SuppressWarnings("unchecked")
	private void scanClassSettings(Map> triggers, KeyWithWorkerData internalKey, Object instance) {
		for (Class clazz : classSettings.keySet()) {
			for (TriggerConfig config : classSettings.get(clazz)) {
				if (clazz.isAssignableFrom(instance.getClass())) {
					triggers.computeIfAbsent(internalKey, $ -> new ArrayList<>())
							.add(new TriggerRegistryRecord(config.severity, config.name, () ->
									((TriggerConfig) config).triggerFunction.apply(instance)));
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void scanKeySettings(Map> triggers, KeyWithWorkerData internalKey, Object instance) {
		Key key = (Key) internalKey.getKey();
		for (TriggerConfig config : keySettings.getOrDefault(key, emptySet())) {
			triggers.computeIfAbsent(internalKey, $ -> new ArrayList<>())
					.add(new TriggerRegistryRecord(config.severity, config.name, () ->
							((TriggerConfig) config).triggerFunction.apply(instance)));
		}
	}

}