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

io.datakernel.crdt.CrdtStorageClient 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;

import io.datakernel.async.service.EventloopService;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.common.exception.StacklessException;
import io.datakernel.csp.ChannelConsumer;
import io.datakernel.csp.net.MessagingWithBinaryStreaming;
import io.datakernel.datastream.StreamConsumer;
import io.datakernel.datastream.StreamSupplier;
import io.datakernel.datastream.csp.ChannelDeserializer;
import io.datakernel.datastream.csp.ChannelSerializer;
import io.datakernel.datastream.stats.StreamStats;
import io.datakernel.datastream.stats.StreamStatsBasic;
import io.datakernel.datastream.stats.StreamStatsDetailed;
import io.datakernel.eventloop.ConnectCallback;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.eventloop.jmx.EventloopJmxMBeanEx;
import io.datakernel.eventloop.net.SocketSettings;
import io.datakernel.jmx.api.JmxAttribute;
import io.datakernel.jmx.api.JmxOperation;
import io.datakernel.net.AsyncTcpSocketImpl;
import io.datakernel.promise.Promise;
import io.datakernel.serializer.BinarySerializer;
import org.jetbrains.annotations.NotNull;

import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.function.Function;

import static io.datakernel.crdt.CrdtMessaging.*;
import static io.datakernel.crdt.CrdtMessaging.CrdtMessages.PING;
import static io.datakernel.crdt.CrdtMessaging.CrdtResponses.*;
import static io.datakernel.csp.binary.ByteBufSerializer.ofJsonCodec;

public final class CrdtStorageClient, S> implements CrdtStorage, EventloopService, EventloopJmxMBeanEx {
	private final Eventloop eventloop;
	private final InetSocketAddress address;
	private final CrdtDataSerializer serializer;
	private final BinarySerializer keySerializer;

	private SocketSettings socketSettings = SocketSettings.create();

	// 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();
	// endregion

	//region creators
	private CrdtStorageClient(Eventloop eventloop, InetSocketAddress address, CrdtDataSerializer serializer) {
		this.eventloop = eventloop;
		this.address = address;
		this.serializer = serializer;

		keySerializer = serializer.getKeySerializer();
	}

	public static , S> CrdtStorageClient create(Eventloop eventloop, InetSocketAddress address,
																			  CrdtDataSerializer serializer) {
		return new CrdtStorageClient<>(eventloop, address, serializer);
	}

	public static , S> CrdtStorageClient create(Eventloop eventloop, InetSocketAddress address,
																			  BinarySerializer keySerializer, BinarySerializer stateSerializer) {
		return new CrdtStorageClient<>(eventloop, address, new CrdtDataSerializer<>(keySerializer, stateSerializer));
	}

	public CrdtStorageClient withSocketSettings(SocketSettings socketSettings) {
		this.socketSettings = socketSettings;
		return this;
	}
	//endregion

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

	@Override
	public Promise>> upload() {
		return connect()
				.then(messaging ->
						messaging.send(CrdtMessages.UPLOAD)
								.map($ -> {
									ChannelConsumer consumer = messaging.sendBinaryStream()
											.withAcknowledgement(ack -> ack
													.then($2 -> messaging.receive())
													.then(simpleHandler(UPLOAD_FINISHED)));
									return StreamConsumer.>ofSupplier(supplier ->
											supplier.transformWith(detailedStats ? uploadStats : uploadStatsDetailed)
													.transformWith(ChannelSerializer.create(serializer))
													.streamTo(consumer))
											.withLateBinding();
								}));
	}

	@Override
	public Promise>> download(long timestamp) {
		return connect()
				.then(messaging -> messaging.send(new Download(timestamp))
						.then($ -> messaging.receive())
						.then(response -> {
							if (response == null) {
								return Promise.ofException(new IllegalStateException("Unexpected end of stream"));
							}
							if (response.getClass() == DownloadStarted.class) {
								return Promise.complete();
							}
							if (response instanceof ServerError) {
								return Promise.ofException(new StacklessException(CrdtStorageClient.class, ((ServerError) response).getMsg()));
							}
							return Promise.ofException(new IllegalStateException("Received message " + response + " instead of " + DownloadStarted.class.getSimpleName()));
						})
						.map($ ->
								messaging.receiveBinaryStream()
										.transformWith(ChannelDeserializer.create(serializer))
										.transformWith(detailedStats ? downloadStats : downloadStatsDetailed)
										.withEndOfStream(eos -> eos
												.then($2 -> messaging.sendEndOfStream())
												.whenResult($2 -> messaging.close()))
										.withLateBinding()));
	}

	@Override
	public Promise> remove() {
		return connect()
				.then(messaging ->
						messaging.send(CrdtMessages.REMOVE)
								.map($ -> {
									ChannelConsumer consumer = messaging.sendBinaryStream()
											.withAcknowledgement(ack -> ack
													.then($2 -> messaging.receive())
													.then(simpleHandler(REMOVE_FINISHED)));
									return StreamConsumer.ofSupplier(supplier ->
											supplier.transformWith(detailedStats ? removeStats : removeStatsDetailed)
													.transformWith(ChannelSerializer.create(keySerializer))
													.streamTo(consumer))
											.withLateBinding();
								}));
	}

	@Override
	public Promise ping() {
		return connect()
				.then(messaging -> messaging.send(PING)
						.then($ -> messaging.receive())
						.then(simpleHandler(PONG)));
	}

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

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

	private Function> simpleHandler(CrdtResponse expected) {
		return response -> {
			if (response == null) {
				return Promise.ofException(new IllegalStateException("Unexpected end of stream"));
			}
			if (response == expected) {
				return Promise.complete();
			}
			if (response instanceof ServerError) {
				return Promise.ofException(new StacklessException(CrdtStorageClient.class, ((ServerError) response).getMsg()));
			}
			return Promise.ofException(new IllegalStateException("Received message " + response + " instead of " + expected));
		};
	}

	private Promise> connect() {
		return Promise.ofCallback(cb ->
				eventloop.connect(address, new ConnectCallback() {
					@Override
					public void onConnect(@NotNull SocketChannel channel) {
						AsyncTcpSocketImpl socket = AsyncTcpSocketImpl.wrapChannel(eventloop, channel, socketSettings);
						cb.set(MessagingWithBinaryStreaming.create(socket, ofJsonCodec(RESPONSE_CODEC, MESSAGE_CODEC)));
					}

					@Override
					public void onException(@NotNull Throwable e) {
						cb.setException(e);
					}
				}));
	}

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

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

	@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;
	}
	// endregion
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy