com.eventsourcing.postgresql.PostgreSQLJournal Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eventsourcing-postgresql Show documentation
Show all versions of eventsourcing-postgresql Show documentation
Event capture and querying framework for Java
/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* 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 http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.postgresql;
import com.eventsourcing.*;
import com.eventsourcing.layout.Layout;
import com.eventsourcing.layout.Property;
import com.eventsourcing.layout.TypeHandler;
import com.eventsourcing.layout.binary.BinarySerialization;
import com.google.common.base.Joiner;
import com.google.common.io.BaseEncoding;
import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.AbstractService;
import com.googlecode.cqengine.index.support.CloseableIterator;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.Value;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.sql.DataSource;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Savepoint;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.eventsourcing.postgresql.PostgreSQLSerialization.*;
@Component(property = "type=PostgreSQLJournal", service = Journal.class)
public class PostgreSQLJournal extends AbstractService implements Journal {
@Reference
protected DataSourceProvider dataSourceProvider;
private DataSource dataSource;
@Getter @Setter
private Repository repository;
private EntityLayoutExtractor entityLayoutExtractor = new EntityLayoutExtractor();
@Activate
protected void activate() {
dataSource = dataSourceProvider.getDataSource();
}
public PostgreSQLJournal() {}
public PostgreSQLJournal(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override public void onCommandsAdded(Set> commands) {
commands.forEach(entityLayoutExtractor);
}
@Override public void onEventsAdded(Set> events) {
events.forEach(entityLayoutExtractor);
}
@Value
static class Transaction implements Journal.Transaction {
private final Connection connection;
private final Savepoint savepoint;
@SneakyThrows
public Transaction(DataSource dataSource) {
connection = dataSource.getConnection();
connection.setAutoCommit(false);
savepoint = connection.setSavepoint();
}
@SneakyThrows
@Override public void commit() {
connection.releaseSavepoint(savepoint);
connection.commit();
connection.close();
}
@SneakyThrows
@Override public void rollback() {
connection.rollback(savepoint);
connection.releaseSavepoint(savepoint);
connection.close();
}
}
@Override public Journal.Transaction beginTransaction() {
return new Transaction(dataSource);
}
@Override public Command journal(Journal.Transaction tx, Command command) {
Layout layout = getLayout(command.getClass());
String encoded = BaseEncoding.base16().encode(layout.getHash());
insertFunctions.get(encoded).apply(command, ((Transaction)tx).getConnection());
BinarySerialization serialization = BinarySerialization.getInstance();
ByteBuffer s = serialization.getSerializer(command.getClass()).serialize(command);
s.rewind();
Command command1 = (Command) serialization.getDeserializer(command.getClass()).deserialize(s);
command1.uuid(command.uuid());
return command1;
}
@Override public Event journal(Journal.Transaction tx, Event event) {
Layout layout = getLayout(event.getClass());
String encoded = BaseEncoding.base16().encode(layout.getHash());
InsertFunction insert = insertFunctions.get(encoded);
insert.apply(event, ((Transaction)tx).getConnection());
BinarySerialization serialization = BinarySerialization.getInstance();
ByteBuffer s = serialization.getSerializer(event.getClass()).serialize(event);
s.rewind();
Event event1 = (Event) serialization.getDeserializer(event.getClass()).deserialize(s);
event1.uuid(event.uuid());
return event1;
}
@SneakyThrows
@Override public Optional get(UUID uuid) {
Optional result;
Connection connection = dataSource.getConnection();
refreshConnectionRegistry(connection);
PreparedStatement s = connection
.prepareStatement("SELECT layout FROM layouts_v1 WHERE uuid = ?::UUID");
s.setString(1, uuid.toString());
try (ResultSet resultSet = s.executeQuery()) {
if (resultSet.next()) {
byte[] bytes = resultSet.getBytes(1);
String hash = BaseEncoding.base16().encode(bytes);
ReaderFunction reader = readerFunctions.get(hash);
Layout> layout = getLayout(bytes);
String columns = Joiner.on(", ")
.join(layout.getProperties().stream()
.map(p -> "\"" + p.getName() + "\"").collect(Collectors.toList()));
String query = "SELECT " + columns + " FROM layout_v1_" + hash + " WHERE uuid = ?::UUID";
PreparedStatement s1 = connection.prepareStatement(query);
s1.setString(1, uuid.toString());
try (ResultSet rs = s1.executeQuery()) {
rs.next();
Entity o = (Entity) reader.apply(rs);
o.uuid(uuid);
result = Optional.of((T) o);
}
s1.close();
} else {
result = Optional.empty();
}
}
s.close();
connection.close();
return result;
}
@Override public > CloseableIterator> commandIterator(Class klass) {
return entityIterator(klass);
}
@Override public CloseableIterator> eventIterator(Class klass) {
return entityIterator(klass);
}
@SneakyThrows
private CloseableIterator> entityIterator(Class klass) {
Connection connection = dataSource.getConnection();
Layout layout = getLayout(klass);
String hash = BaseEncoding.base16().encode(layout.getHash());
PreparedStatement s = connection.prepareStatement("SELECT uuid FROM layout_v1_" + hash);
return new EntityIterator<>(this, s, connection);
}
static private class EntityIterator extends PostgreSQLStatementIterator> {
private final Journal journal;
public EntityIterator(Journal journal, PreparedStatement statement,
Connection connection) {
super(statement, connection, true);
this.journal = journal;
}
@SneakyThrows
@Override
public EntityHandle fetchNext() {
return new JournalEntityHandle<>(journal, UUID.fromString(resultSet.getString(1)));
}
}
@SneakyThrows
@Override public void clear() {
Connection connection = dataSource.getConnection();
layoutsByHash.keySet().forEach(new Consumer() {
@SneakyThrows
@Override public void accept(String hash) {
PreparedStatement s = connection.prepareStatement("DELETE FROM layout_v1_" + hash);
s.execute();
s.close();
}
});
PreparedStatement check = connection
.prepareStatement("SELECT * from pg_catalog.pg_tables WHERE tablename = 'layouts' AND schemaname = ?");
check.setString(1, "eventsourcing");
try (ResultSet resultSet = check.executeQuery()) {
if (resultSet.next()) {
PreparedStatement s = connection.prepareStatement("DELETE FROM layouts_v1");
s.execute();
s.close();
}
}
check.close();
connection.close();
}
@SneakyThrows
@Override public long size(Class klass) {
Layout layout = getLayout(klass);
String hash = BaseEncoding.base16().encode(layout.getHash());
Connection connection = dataSource.getConnection();
PreparedStatement s = connection
.prepareStatement("SELECT count(uuid) FROM layout_v1_" + hash);
long size;
try (ResultSet resultSet = s.executeQuery()) {
resultSet.next();
size = resultSet.getLong(1);
}
s.close();
connection.close();
return size;
}
@Override public boolean isEmpty(Class klass) {
return size(klass) == 0;
}
@Override protected void doStart() {
if (repository == null) {
notifyFailed(new IllegalStateException("repository == null"));
}
if (dataSource == null) {
notifyFailed(new IllegalStateException("dataSource == null"));
}
ensureLatestSchemaVersion();
notifyStarted();
}
@SneakyThrows
private void ensureLatestSchemaVersion() {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement s = connection
.prepareStatement("CREATE TABLE IF NOT EXISTS layouts_v1 (\n" +
" uuid UUID PRIMARY KEY,\n" +
" layout BYTEA NOT NULL\n" +
")")) {
s.executeUpdate();
}
String timestampFunction = CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream
("timestamp_function.sql")));
try (PreparedStatement s = connection.prepareStatement(timestampFunction)) {
s.executeUpdate();
}
}
}
@Override protected void doStop() {
notifyStopped();
}
private Map insertFunctions = new ConcurrentHashMap<>();
private Map readerFunctions = new ConcurrentHashMap<>();
private class ReaderFunction implements Function {
private final Layout layout;
public ReaderFunction(Layout> layout) {
this.layout = layout;
}
@SneakyThrows
@Override public Object apply(ResultSet resultSet) {
AtomicInteger i = new AtomicInteger(1);
List extends Property>> properties = layout.getProperties();
Map, Object> props = new HashMap<>();
for (Property property : properties) {
TypeHandler typeHandler = property.getTypeHandler();
props.put(property, getValue(resultSet, i, typeHandler));
}
return layout.instantiate(props);
}
}
private class InsertFunction implements BiFunction
© 2015 - 2025 Weber Informatics LLC | Privacy Policy