/**
* 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.inmem;
import com.eventsourcing.*;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.layout.ObjectDeserializer;
import com.eventsourcing.layout.ObjectSerializer;
import com.eventsourcing.layout.Serialization;
import com.eventsourcing.layout.binary.BinarySerialization;
import com.eventsourcing.utils.CloseableWrappingIterator;
import com.google.common.collect.Iterators;
import com.google.common.util.concurrent.AbstractService;
import com.googlecode.cqengine.index.support.CloseableIterator;
import com.googlecode.cqengine.query.option.QueryOptions;
import lombok.Getter;
import lombok.Setter;
import org.osgi.service.component.annotations.Component;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Memory-based {@link Journal} implementation. Not meant to be used in production.
*/
@Component(property = {"type=MemoryJournal"}, service = Journal.class)
public class MemoryJournal extends AbstractService implements Journal {
private static final Serialization serialization = BinarySerialization.getInstance();
@Getter @Setter
private Repository repository;
private Map<UUID, Command> commands = new ConcurrentHashMap<>();
private Map<UUID, Event> events = new ConcurrentHashMap<>();
@Override
protected void doStart() {
if (repository == null) {
notifyFailed(new IllegalStateException("repository == null"));
}
notifyStarted();
}
@Override
protected void doStop() {
notifyStopped();
}
static class Transaction implements Journal.Transaction {
private final Map<UUID, Event> events = new HashMap<>();
@Setter
private Command command;
private final MemoryJournal journal;
Transaction(MemoryJournal journal) {this.journal = journal;}
@Override public void rollback() {
}
@Override public void commit() {
journal.events.putAll(events);
journal.commands.put(command.uuid(), command);
}
}
@Override public <S, T> Command<S, T> journal(Journal.Transaction tx, Command<S, T> command) {
ObjectSerializer<Command> serializer = serialization.getSerializer(command.getClass());
ObjectDeserializer<Command> deserializer = serialization.getDeserializer(command.getClass());
ByteBuffer buffer = serializer.serialize(command);
buffer.rewind();
Command command1 = deserializer.deserialize(buffer);
command1.uuid(command.uuid());
((Transaction) tx).setCommand(command1);
return command1;
}
@Override public Event journal(Journal.Transaction tx, Event event) {
ObjectSerializer<Event> serializer = serialization.getSerializer(event.getClass());
ObjectDeserializer<Event> deserializer = serialization.getDeserializer(event.getClass());
ByteBuffer buffer = serializer.serialize(event);
buffer.rewind();
Event event1 = deserializer.deserialize(buffer);
event1.uuid(event.uuid());
((Transaction) tx).events.put(event1.uuid(), event1);
return event1;
}
@Override public Journal.Transaction beginTransaction() {
return new Transaction(this);
}
@Getter
private final Properties properties = new Properties() {
@Getter
private Optional<HybridTimestamp> repositoryTimestamp = Optional.empty();
@Override public void setRepositoryTimestamp(HybridTimestamp timestamp) {
repositoryTimestamp = Optional.of(timestamp);
}
};
@Override @SuppressWarnings("unchecked")
public <T extends Entity> Optional<T> get(UUID uuid) {
if (commands.containsKey(uuid)) {
return Optional.of((T) commands.get(uuid));
}
if (events.containsKey(uuid)) {
return Optional.of((T) events.get(uuid));
}
return Optional.empty();
}
@Override
public <T extends Command<?, ?>> CloseableIterator<EntityHandle<T>> commandIterator(Class<T> klass, QueryOptions queryOptions) {
return new CloseableWrappingIterator<>(commands.values().stream()
.filter(command -> klass.isAssignableFrom(command.getClass()))
.map(command -> (EntityHandle<T>) new JournalEntityHandle<T>(
this, command.uuid())).iterator());
}
@Override
public <T extends Event> CloseableIterator<EntityHandle<T>> eventIterator(Class<T> klass, QueryOptions queryOptions) {
return new CloseableWrappingIterator<>(events.values().stream()
.filter(event -> klass.isAssignableFrom(event.getClass()))
.map(event -> (EntityHandle<T>) new JournalEntityHandle<T>(this,
event.uuid()))
.iterator());
}
@Override
public void clear() {
events.clear();
commands.clear();
}
@Override @SuppressWarnings("unchecked")
public <T extends Entity> long size(Class<T> klass) {
if (Event.class.isAssignableFrom(klass)) {
return Iterators.size(eventIterator((Class<Event>) klass));
}
if (Command.class.isAssignableFrom(klass)) {
return Iterators.size(commandIterator((Class<Command<?, ?>>) klass));
}
return 0;
}
@Override @SuppressWarnings("unchecked")
public <T extends Entity> boolean isEmpty(Class<T> klass) {
if (Event.class.isAssignableFrom(klass)) {
return !eventIterator((Class<Event>) klass).hasNext();
}
if (Command.class.isAssignableFrom(klass)) {
return !commandIterator((Class<Command<?, ?>>) klass).hasNext();
}
return true;
}
}