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

io.datakernel.crdt.local.CrdtStorageMap Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (C) 2015-2019 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.crdt.local;

import io.datakernel.async.service.EventloopService;
import io.datakernel.common.Initializable;
import io.datakernel.crdt.CrdtData;
import io.datakernel.crdt.CrdtFilter;
import io.datakernel.crdt.CrdtFunction;
import io.datakernel.crdt.CrdtStorage;
import io.datakernel.crdt.primitives.CrdtType;
import io.datakernel.datastream.StreamConsumer;
import io.datakernel.datastream.StreamSupplier;
import io.datakernel.datastream.stats.StreamStats;
import io.datakernel.datastream.stats.StreamStatsBasic;
import io.datakernel.datastream.stats.StreamStatsDetailed;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.eventloop.jmx.EventStats;
import io.datakernel.eventloop.jmx.EventloopJmxMBeanEx;
import io.datakernel.jmx.api.JmxAttribute;
import io.datakernel.jmx.api.JmxOperation;
import io.datakernel.promise.Promise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Duration;
import java.util.Iterator;
import java.util.Objects;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.stream.Stream;

public final class CrdtStorageMap, S> implements CrdtStorage, Initializable>, EventloopService, EventloopJmxMBeanEx {
	private static final Duration DEFAULT_SMOOTHING_WINDOW = Duration.ofMinutes(5);

	private final Eventloop eventloop;
	private final CrdtFunction function;

	private CrdtFilter filter = $ -> true;

	private final SortedMap> storage = new ConcurrentSkipListMap<>();

	// region JMX
	private boolean detailedStats;

	private final StreamStatsBasic> uploadStats = StreamStats.basic();
	private final StreamStatsDetailed> uploadStatsDetailed = StreamStats.detailed();
	private final StreamStatsBasic> downloadStats = StreamStats.basic();
	private final StreamStatsDetailed> downloadStatsDetailed = StreamStats.detailed();
	private final StreamStatsBasic removeStats = StreamStats.basic();
	private final StreamStatsDetailed removeStatsDetailed = StreamStats.detailed();

	private final EventStats singlePuts = EventStats.create(DEFAULT_SMOOTHING_WINDOW);
	private final EventStats singleGets = EventStats.create(DEFAULT_SMOOTHING_WINDOW);
	private final EventStats singleRemoves = EventStats.create(DEFAULT_SMOOTHING_WINDOW);
	// endregion

	private CrdtStorageMap(Eventloop eventloop, CrdtFunction function) {
		this.eventloop = eventloop;
		this.function = function;
	}

	public static , S> CrdtStorageMap create(Eventloop eventloop, CrdtFunction crdtFunction) {
		return new CrdtStorageMap<>(eventloop, crdtFunction);
	}

	public static , S extends CrdtType> CrdtStorageMap create(Eventloop eventloop) {
		return new CrdtStorageMap<>(eventloop, CrdtFunction.ofCrdtType());
	}

	@NotNull
	@Override
	public Eventloop getEventloop() {
		return eventloop;
	}

	@SuppressWarnings("deprecation") // StreamConsumer#of
	@Override
	public Promise>> upload() {
		return Promise.of(StreamConsumer.of(this::doPut)
				.transformWith(detailedStats ? uploadStatsDetailed : uploadStats)
				.withLateBinding());
	}

	@Override
	public Promise>> download(long timestamp) {
		return Promise.of(StreamSupplier.ofStream(extract(timestamp))
				.transformWith(detailedStats ? downloadStatsDetailed : downloadStats)
				.withLateBinding());
	}

	@SuppressWarnings("deprecation") // StreamConsumer#of
	@Override
	public Promise> remove() {
		return Promise.of(StreamConsumer.of(storage::remove)
				.transformWith(detailedStats ? removeStatsDetailed : removeStats)
				.withLateBinding());
	}

	@Override
	public Promise ping() {
		return Promise.complete();
	}

	@NotNull
	@Override
	public Promise start() {
		return Promise.complete();
	}

	@NotNull
	@Override
	public Promise stop() {
		return Promise.complete();
	}

	private Stream> extract(long timestamp) {
		Stream> stream = storage.values().stream();
		if (timestamp == 0) {
			return stream;
		}
		return stream
				.map(data -> {
					S partial = function.extract(data.getState(), timestamp);
					return partial != null ? new CrdtData<>(data.getKey(), partial) : null;
				})
				.filter(Objects::nonNull);
	}

	private void doPut(CrdtData data) {
		K key = data.getKey();
		storage.merge(key, data, (a, b) -> {
			S merged = function.merge(a.getState(), b.getState());
			return filter.test(merged) ? new CrdtData<>(key, merged) : null;
		});
	}

	public void put(K key, S state) {
		put(new CrdtData<>(key, state));
	}

	public void put(CrdtData data) {
		singlePuts.recordEvent();
		doPut(data);
	}

	@Nullable
	public S get(K key) {
		singleGets.recordEvent();
		CrdtData data = storage.get(key);
		return data != null ? data.getState() : null;
	}

	public boolean remove(K key) {
		singleRemoves.recordEvent();
		return storage.remove(key) != null;
	}

	public Iterator> iterator(long timestamp) {
		Iterator> iterator = extract(timestamp).iterator();

		// had to hook the remove so it would be reflected in the storage
		return new Iterator>() {
			private CrdtData current;

			@Override
			public boolean hasNext() {
				return iterator.hasNext();
			}

			@Override
			public CrdtData next() {
				return current = iterator.next();
			}

			@Override
			public void remove() {
				if (current != null) {
					CrdtStorageMap.this.remove(current.getKey());
				}
				iterator.remove();
			}
		};
	}

	public Iterator> iterator() {
		return iterator(0);
	}

	// region JMX
	@JmxOperation
	public void startDetailedMonitoring() {
		detailedStats = true;
	}

	@JmxOperation
	public void stopDetailedMonitoring() {
		detailedStats = false;
	}

	@JmxAttribute
	public boolean isDetailedStats() {
		return detailedStats;
	}

	@JmxAttribute
	public StreamStatsBasic getUploadStats() {
		return uploadStats;
	}

	@JmxAttribute
	public StreamStatsDetailed getUploadStatsDetailed() {
		return uploadStatsDetailed;
	}

	@JmxAttribute
	public StreamStatsBasic getDownloadStats() {
		return downloadStats;
	}

	@JmxAttribute
	public StreamStatsDetailed getDownloadStatsDetailed() {
		return downloadStatsDetailed;
	}

	@JmxAttribute
	public StreamStatsBasic getRemoveStats() {
		return removeStats;
	}

	@JmxAttribute
	public StreamStatsDetailed getRemoveStatsDetailed() {
		return removeStatsDetailed;
	}

	@JmxAttribute
	public EventStats getSinglePuts() {
		return singlePuts;
	}

	@JmxAttribute
	public EventStats getSingleGets() {
		return singleGets;
	}

	@JmxAttribute
	public EventStats getSingleRemoves() {
		return singleRemoves;
	}
	// endregion
}