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

io.vlingo.xoom.symbio.store.state.jdbc.JDBCEntriesBatchWriter Maven / Gradle / Ivy

There is a newer version: 1.11.1
Show newest version
// Copyright © 2012-2021 VLINGO LABS. All rights reserved.
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain
// one at https://mozilla.org/MPL/2.0/.

package io.vlingo.xoom.symbio.store.state.jdbc;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import io.vlingo.xoom.actors.Logger;
import io.vlingo.xoom.common.Failure;
import io.vlingo.xoom.common.Outcome;
import io.vlingo.xoom.common.Success;
import io.vlingo.xoom.symbio.BaseEntry;
import io.vlingo.xoom.symbio.Entry;
import io.vlingo.xoom.symbio.State;
import io.vlingo.xoom.symbio.store.Result;
import io.vlingo.xoom.symbio.store.StorageException;
import io.vlingo.xoom.symbio.store.dispatch.Dispatchable;
import io.vlingo.xoom.symbio.store.dispatch.Dispatcher;
import io.vlingo.xoom.symbio.store.dispatch.DispatcherControl;

public class JDBCEntriesBatchWriter implements JDBCEntriesWriter {
	private final JDBCStorageDelegate delegate;
	@SuppressWarnings("unused")
  private final List, ? extends State>>> dispatchers;
	private final DispatcherControl dispatcherControl;
	private final BatchEntries batchEntries;
	private Logger logger;

	public JDBCEntriesBatchWriter(JDBCStorageDelegate delegate, int maxBatchEntries) {
		this(delegate, null, null, maxBatchEntries);
	}

	public JDBCEntriesBatchWriter(JDBCStorageDelegate delegate,
								  List, ? extends State>>> dispatchers,
								  DispatcherControl dispatcherControl,
								  int maxBatchEntries) {
		this.delegate = delegate;
		this.dispatchers = dispatchers;
		this.dispatcherControl = dispatcherControl;
		this.batchEntries = new BatchEntries(maxBatchEntries);
	}

	@Override
	public void appendEntries(String storeName, List> entries, State.TextState rawState, Consumer> postAppendAction) {
		batchEntries.add(new BatchEntry(storeName, entries, rawState, postAppendAction));
		if (batchEntries.capacityExceeded()) {
			flush();
		}
	}

	@Override
	public void flush() {
		if (batchEntries.size() > 0) {
			appendBatchedEntries();

			try {
				delegate.beginWrite();

				Map> states = batchEntries.states();
				for (Map.Entry> storeStates : states.entrySet()) {
					final PreparedStatement writeStatesStatement = delegate.writeExpressionFor(storeStates.getKey(), storeStates.getValue());
					writeStatesStatement.executeBatch();
					writeStatesStatement.clearBatch();
				}

				List, State>> dispatchables = batchEntries.collectDispatchables();
				final PreparedStatement writeDispatchablesStatement = delegate.dispatchableWriteExpressionFor(dispatchables);
				writeDispatchablesStatement.executeBatch();
				writeDispatchablesStatement.clearBatch();

				delegate.complete();
				batchEntries.completedWith(Success.of(Result.Success));
				batchEntries.clear();
			} catch (Exception e) {
				logger.error(getClass().getSimpleName() + " appendEntries() failed because: " + e.getMessage(), e);
				batchEntries.completedWith(Failure.of(new StorageException(Result.Error, e.getMessage(), e)));
				delegate.fail();
			}
		}
	}

	@Override
	public void stop() {
		// flush batched entries if any
		flush();

		if (dispatcherControl != null) {
			dispatcherControl.stop();
		}

		// delegate is closed in JDBCStateStoreActor
	}

	@Override
	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	@SuppressWarnings("rawtypes")
  private void appendBatchedEntries() {
		List> allEntries = batchEntries.collectEntries();
		if (allEntries.size() > 0) {
			try {
				PreparedStatement appendStatement = delegate.appendExpressionFor(allEntries);
				final int[] countList = appendStatement.executeBatch();
				if (Arrays.stream(countList).anyMatch(id -> id == -1L)) {
					final String message = "xoom-symbio-jdbc: Failed append entries.";
					logger.error(message);
					throw new IllegalStateException(message);
				}

				ResultSet resultSet = appendStatement.getGeneratedKeys();
				for (int i = 0; resultSet.next(); i++) {
					long id = resultSet.getLong(1);
					((BaseEntry) allEntries.get(i)).__internal__setId(Long.toString(id));
				}

				appendStatement.clearBatch();
			} catch (Exception e) {
				final String message = "xoom-symbio-jdbc: Failed to append entries because: " + e.getMessage();
				logger.error(message, e);
				throw new IllegalStateException(message, e);
			}
		}
	}

	static class BatchEntries {
		private final List entries;
		private final int maxCapacity;

		BatchEntries(int maxCapacity) {
			this.entries = new ArrayList<>(maxCapacity);
			this.maxCapacity = maxCapacity;

			if (maxCapacity <= 0) {
				throw new IllegalArgumentException("Illegal capacity: " + maxCapacity);
			}
		}

		void add(BatchEntry entry) {
			entries.add(entry);
		}

		boolean capacityExceeded() {
			return entries.size() >= maxCapacity;
		}

		List, State>> collectDispatchables() {
			return entries.stream()
					.map(batch -> batch.getDispatchable())
					.collect(Collectors.toList());
		}

		List> collectEntries() {
			return entries.stream()
					.map(batch -> batch.entries)
					.reduce(new ArrayList>(), (collected, current) -> {
						collected.addAll(current);
						return collected;
					});
		}

		Map> states() {
			return entries.stream()
					.collect(Collectors.groupingBy((BatchEntry batch) -> batch.storeName,
							Collectors.mapping(batch -> batch.rawState, Collectors.toList())));
		}

		void completedWith(Outcome outcome) {
			entries.stream()
					.forEach(batch -> batch.postAppendAction.accept(outcome));
		}

		void clear() {
			entries.clear();
		}

		int size() {
			return entries.size();
		}
	}

	static class BatchEntry {
		final String storeName;
		final List> entries;
		final State.TextState rawState;
		final Consumer> postAppendAction;

		private Dispatchable, State> dispatchable = null;
		boolean failed = false;

		BatchEntry(String storeName, List> entries, State.TextState rawState, Consumer> postAppendAction) {
			this.storeName = storeName;
			this.entries = entries;
			this.rawState = rawState;
			this.postAppendAction = postAppendAction;
		}

		public Dispatchable, State> getDispatchable() {
			if (dispatchable == null) {
				final String dispatchId = storeName + ":" + rawState.id;
				dispatchable = new Dispatchable<>(dispatchId, LocalDateTime.now(), rawState.asTextState(), entries);
			}

			return dispatchable;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy