/**
* 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.migrations;
import com.eventsourcing.*;
import com.eventsourcing.events.EventCausalityEstablished;
import com.eventsourcing.layout.Layout;
import com.eventsourcing.migrations.events.EntityLayoutIntroduced;
import com.eventsourcing.migrations.events.EntityLayoutReplaced;
import com.googlecode.cqengine.resultset.ResultSet;
import lombok.SneakyThrows;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static com.eventsourcing.queries.QueryFactory.all;
import static com.eventsourcing.queries.QueryFactory.equal;
public class LayoutMigration<A extends Event, B extends Event> {
private Layout<A> oldLayout;
private Layout<B> newLayout;
private Function<A, B> transformation;
private final Class<A> oldClass;
private final Class<B> newClass;
private final boolean includeLayout;
@SneakyThrows
public LayoutMigration(Class<A> oldClass, Class<B> newClass, Function<A, B> transformation, boolean includeLayout) {
this.includeLayout = includeLayout;
this.oldLayout = Layout.forClass(oldClass);
this.oldClass = oldClass;
this.newLayout = Layout.forClass(newClass);
this.newClass = newClass;
this.transformation = transformation;
}
public LayoutMigration(Class<A> oldClass, Class<B> newClass, Function<A, B> transformation) {
this(oldClass, newClass, transformation, true);
}
public Stream<? extends Event> events(Repository repository, LockProvider lockProvider) throws Exception {
Lock lock = lockProvider.lock(oldClass.getName());
Stream<? extends Event> acc = Stream.empty();
Optional<EntityLayoutIntroduced> oldLayoutIntroduciton = layoutIntroduction(repository, oldLayout);
if (!oldLayoutIntroduciton.isPresent()) {
acc = Stream.concat(acc, Stream.of(makeLayoutIntroduction(oldLayout)));
}
Optional<EntityLayoutIntroduced> newLayoutIntroduction = layoutIntroduction(repository, newLayout);
UUID newLayoutIntroductionUUID;
if (!newLayoutIntroduction.isPresent()) {
EntityLayoutIntroduced introduction = makeLayoutIntroduction(newLayout);
acc = Stream.concat(acc, Stream.of(introduction));
newLayoutIntroductionUUID = introduction.uuid();
} else {
newLayoutIntroductionUUID = newLayoutIntroduction.get().uuid();
}
EntityLayoutReplaced replacement = EntityLayoutReplaced.builder()
.fingerprint(oldLayout.getHash())
.replacement(newLayoutIntroductionUUID)
.build();
acc = Stream.concat(acc, Stream.of(replacement));
ResultSet<EntityHandle<A>> resultSet = repository.query(oldClass, all(oldClass));
Iterator<EntityHandle<A>> iterator = resultSet.iterator();
Stream<Event> stream = StreamSupport
.stream(Spliterators.spliterator(iterator, resultSet.size(), Spliterator.IMMUTABLE), false)
.flatMap(h -> {
B transformed = transformation.apply(h.get());
try (ResultSet<EntityHandle<EventCausalityEstablished>> causality = repository
.query(EventCausalityEstablished.class, equal(EventCausalityEstablished.EVENT, h.uuid()))) {
Stream<EntityHandle<EventCausalityEstablished>> causalityStream = StreamSupport
.stream(Spliterators.spliterator(causality.iterator(), causality.size(),
Spliterator.IMMUTABLE), false);
Function<EntityHandle<EventCausalityEstablished>, Event> entityHandleFunction = handle ->
EventCausalityEstablished.builder()
.event(transformed.uuid())
.command(handle.get().command()).build();
return Stream.concat(Stream.of(transformed), causalityStream.map(entityHandleFunction));
}
});
stream.onClose(resultSet::close);
acc = Stream.concat(acc, stream);
lock.unlock();
return acc;
}
private Optional<EntityLayoutIntroduced> layoutIntroduction(Repository repository, Layout<?> layout) {
try (ResultSet<EntityHandle<EntityLayoutIntroduced>> resultSet = repository
.query(EntityLayoutIntroduced.class,
equal(EntityLayoutIntroduced.FINGERPRINT, layout.getHash()))) {
if (resultSet.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(resultSet.uniqueResult().get());
}
}
}
private EntityLayoutIntroduced makeLayoutIntroduction(Layout<?> layout) {
EntityLayoutIntroduced.EntityLayoutIntroducedBuilder builder = EntityLayoutIntroduced.builder();
builder.fingerprint(layout.getHash());
if (includeLayout) {
builder.layout(Optional.of(layout));
}
return builder.build();
}
}