Java tutorial
/** * 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.repository; import boguspackage.BogusCommand; import boguspackage.BogusEvent; import com.eventsourcing.*; import com.eventsourcing.events.CommandTerminatedExceptionally; import com.eventsourcing.events.EventCausalityEstablished; import com.eventsourcing.events.JavaExceptionOccurred; import com.eventsourcing.hlc.HybridTimestamp; import com.eventsourcing.hlc.NTPServerTimeProvider; import com.eventsourcing.hlc.PhysicalTimeProvider; import com.eventsourcing.index.*; import com.eventsourcing.inmem.MemoryIndexEngine; import com.eventsourcing.layout.LayoutConstructor; import com.eventsourcing.migrations.events.EntityLayoutIntroduced; import com.eventsourcing.repository.commands.IntroduceEntityLayouts; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.AbstractService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Service; import com.googlecode.cqengine.IndexedCollection; import com.googlecode.cqengine.resultset.ResultSet; import lombok.*; import lombok.experimental.Accessors; import org.apache.commons.net.ntp.TimeStamp; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import static com.eventsourcing.queries.QueryFactory.*; import static com.eventsourcing.index.IndexEngine.IndexFeature.*; import static org.testng.Assert.*; public abstract class RepositoryTest { private Repository repository; private Journal journal; private IndexEngine indexEngine; private LocalLockProvider lockProvider; private NTPServerTimeProvider timeProvider; private TimeStamp startTime; @BeforeClass public void setUpEnv() throws Exception { startTime = new TimeStamp(new Date()); repository = new StandardRepository(); repository.addCommandSetProvider( new PackageCommandSetProvider(new Package[] { RepositoryTest.class.getPackage() })); repository.addEventSetProvider( new PackageEventSetProvider(new Package[] { RepositoryTest.class.getPackage() })); journal = createJournal(); repository.setJournal(journal); timeProvider = new NTPServerTimeProvider(new String[] { "localhost" }); repository.setPhysicalTimeProvider(timeProvider); indexEngine = createIndexEngine(); repository.setIndexEngine(indexEngine); lockProvider = new LocalLockProvider(); repository.setLockProvider(lockProvider); repository.startAsync().awaitRunning(); long size = journal.size(EntityLayoutIntroduced.class); assertTrue(size > 0); // make sure layout introductions don't duplicate repository.publish( new IntroduceEntityLayouts(Iterables.concat(repository.getCommands(), repository.getEvents()))) .join(); assertEquals(journal.size(EntityLayoutIntroduced.class), size); } protected IndexEngine createIndexEngine() { return new MemoryIndexEngine(); } protected abstract Journal createJournal(); @AfterClass public void tearDownEnv() throws Exception { repository.stopAsync().awaitTerminated(); } @BeforeMethod public void setUp() throws Exception { journal.clear(); indexEngine.getIndexedCollection(EntityLayoutIntroduced.class).clear(); repository.publish( new IntroduceEntityLayouts(Iterables.concat(repository.getCommands(), repository.getEvents()))) .join(); } @Accessors(fluent = true) @ToString @Indices({ TestEvent.class, TestEventExtraIndices.class }) public static class TestEvent extends StandardEvent { @Getter private final String string; @Index({ EQ, SC }) public final static SimpleIndex<TestEvent, String> ATTR = SimpleIndex.as(TestEvent::string); @Index({ EQ, SC }) @Deprecated public final static SimpleIndex<TestEvent, String> ATTR_DEP = SimpleIndex.as(TestEvent::string); @Index public final static MultiValueIndex<TestEvent, String> ATTRS = MultiValueIndex.as(TestEvent::strings); public List<String> strings() { return Arrays.asList(string); } @Builder public TestEvent(HybridTimestamp timestamp, String string) { super(timestamp); this.string = string; } } public static class TestEventExtraIndices { public static SimpleIndex<TestEvent, List<String>> ATTRL = TestEvent::strings; } @ToString public static class RepositoryTestCommand extends StandardCommand<Void, String> { @Getter private final String value; @Builder public RepositoryTestCommand(HybridTimestamp timestamp, String value) { super(timestamp); this.value = value == null ? "test" : value; } @Override public EventStream<Void> events() { return EventStream.of(TestEvent.builder().string(value).build()); } @Override public String result() { return "hello, world"; } @Index({ EQ, SC }) public final static SimpleIndex<RepositoryTestCommand, String> ATTR = SimpleIndex .as(RepositoryTestCommand::getValue); } @Test @SneakyThrows public void initialTimestamp() { HybridTimestamp t = repository.getTimestamp(); long ts = t.timestamp(); TimeStamp soon = new TimeStamp(new Date(new Date().toInstant().plus(1, ChronoUnit.SECONDS).toEpochMilli())); TimeStamp t1 = new TimeStamp(ts); assertTrue(HybridTimestamp.compare(t1, startTime) > 0); assertTrue(HybridTimestamp.compare(t1, soon) < 0); } @Test public void discovery() { assertTrue(repository.getCommands().contains(RepositoryTestCommand.class)); } @Test @SneakyThrows public void layoutIntroduction() { try (ResultSet<EntityHandle<EntityLayoutIntroduced>> resultSet = repository .query(EntityLayoutIntroduced.class, all(EntityLayoutIntroduced.class))) { assertTrue(resultSet.isNotEmpty()); } } @Test @SneakyThrows public void basicPublish() { assertEquals("hello, world", repository.publish(RepositoryTestCommand.builder().build()).get()); } @Test @SneakyThrows public void subscribe() { final AtomicBoolean gotEvent = new AtomicBoolean(); final AtomicBoolean gotCommand = new AtomicBoolean(); repository.addEntitySubscriber(new ClassEntitySubscriber<TestEvent>(TestEvent.class) { @Override public void onEntity(EntityHandle<TestEvent> entity) { gotEvent.set(journal.get(entity.uuid()).isPresent()); } }); repository .addEntitySubscriber(new ClassEntitySubscriber<RepositoryTestCommand>(RepositoryTestCommand.class) { @Override public void onEntity(EntityHandle<RepositoryTestCommand> entity) { gotCommand.set(journal.get(entity.uuid()).isPresent()); } }); repository.publish(RepositoryTestCommand.builder().build()).get(); assertTrue(gotEvent.get()); assertTrue(gotCommand.get()); } @Test @SneakyThrows public void timestamping() { IndexedCollection<EntityHandle<TestEvent>> coll = indexEngine.getIndexedCollection(TestEvent.class); coll.clear(); IndexedCollection<EntityHandle<RepositoryTestCommand>> coll1 = indexEngine .getIndexedCollection(RepositoryTestCommand.class); coll1.clear(); repository.publish(RepositoryTestCommand.builder().build()).get(); TestEvent test = coll.retrieve(equal(TestEvent.ATTR, "test")).uniqueResult().get(); assertNotNull(test.timestamp()); RepositoryTestCommand test1 = coll1.retrieve(equal(RepositoryTestCommand.ATTR, "test")).uniqueResult() .get(); assertNotNull(test1.timestamp()); assertTrue(test.timestamp().compareTo(test1.timestamp()) > 0); } @Test @SneakyThrows public void commandTimestamping() { HybridTimestamp timestamp = new HybridTimestamp(timeProvider); timestamp.update(); RepositoryTestCommand command1 = RepositoryTestCommand.builder().value("forced").timestamp(timestamp) .build(); RepositoryTestCommand command2 = RepositoryTestCommand.builder().build(); IndexedCollection<EntityHandle<RepositoryTestCommand>> coll1 = indexEngine .getIndexedCollection(RepositoryTestCommand.class); coll1.clear(); repository.publish(command2).get(); repository.publish(command1).get(); RepositoryTestCommand test1 = coll1.retrieve(equal(RepositoryTestCommand.ATTR, "forced")).uniqueResult() .get(); RepositoryTestCommand test2 = coll1.retrieve(equal(RepositoryTestCommand.ATTR, "test")).uniqueResult() .get(); assertTrue(test1.timestamp().compareTo(test2.timestamp()) < 0); assertTrue(repository.getTimestamp().compareTo(test1.timestamp()) > 0); assertTrue(repository.getTimestamp().compareTo(test2.timestamp()) > 0); } @ToString public static class TimestampingEventCommand extends StandardCommand<Void, String> { private final HybridTimestamp eventTimestamp; @LayoutConstructor public TimestampingEventCommand(HybridTimestamp timestamp) { super(timestamp); eventTimestamp = null; } @Builder public TimestampingEventCommand(HybridTimestamp timestamp, HybridTimestamp eventTimestamp) { super(timestamp); this.eventTimestamp = eventTimestamp; } @Override public EventStream<Void> events() { return EventStream .of(new Event[] { TestEvent.builder().string("test").timestamp(eventTimestamp).build(), TestEvent.builder().string("followup").build() }); } @Override public String result() { return "hello, world"; } } @Test @SneakyThrows public void eventTimestamping() { IndexedCollection<EntityHandle<TestEvent>> coll = indexEngine.getIndexedCollection(TestEvent.class); coll.clear(); HybridTimestamp timestamp = new HybridTimestamp(timeProvider); timestamp.update(); TimestampingEventCommand command = TimestampingEventCommand.builder().eventTimestamp(timestamp).build(); repository.publish(command).get(); TestEvent test = coll.retrieve(equal(TestEvent.ATTR, "test")).uniqueResult().get(); assertTrue(test.timestamp().compareTo(command.timestamp()) < 0); assertTrue(repository.getTimestamp().compareTo(test.timestamp()) > 0); TestEvent followup = coll.retrieve(equal(TestEvent.ATTR, "followup")).uniqueResult().get(); assertTrue(test.timestamp().compareTo(followup.timestamp()) < 0); assertTrue(repository.getTimestamp().compareTo(followup.timestamp()) > 0); } @Test @SneakyThrows public void indexing() { IndexedCollection<EntityHandle<TestEvent>> coll = indexEngine.getIndexedCollection(TestEvent.class); coll.clear(); IndexedCollection<EntityHandle<RepositoryTestCommand>> coll1 = indexEngine .getIndexedCollection(RepositoryTestCommand.class); coll1.clear(); repository.publish(RepositoryTestCommand.builder().build()).get(); assertTrue(coll.retrieve(equal(TestEvent.ATTR, "test")).isNotEmpty()); assertTrue(coll.retrieve(equal(TestEvent.ATTR_DEP, "test")).isNotEmpty()); assertTrue(coll.retrieve(contains(TestEvent.ATTR, "es")).isNotEmpty()); assertEquals(coll.retrieve(equal(TestEvent.ATTR, "test")).uniqueResult().get().string(), "test"); assertEquals(coll.retrieve(equal(TestEvent.ATTRS, "test")).uniqueResult().get().string(), "test"); assertEquals(coll.retrieve(equal(TestEventExtraIndices.ATTRL, Arrays.asList("test"))).uniqueResult().get() .string(), "test"); assertTrue(coll.retrieve(equal(TestEventExtraIndices.ATTRL, Arrays.asList("test1"))).isEmpty()); assertTrue(coll1.retrieve(equal(RepositoryTestCommand.ATTR, "test")).isNotEmpty()); assertTrue(coll1.retrieve(contains(RepositoryTestCommand.ATTR, "es")).isNotEmpty()); } @Accessors(fluent = true) @ToString public static class TestEventWithQueryOptions extends StandardEvent { @Getter private final String string; @Index({ EQ, SC }) public static SimpleIndex<TestEventWithQueryOptions, String> ATTR = SimpleIndex .withQueryOptions((object, queryOptions) -> { if (queryOptions.get(TestEventWithQueryOptions.class) != null) { return "QueryOptions"; } else { return object.string(); } }); @Builder public TestEventWithQueryOptions(String string) { this.string = string; } } @ToString public static class TestEventWithQueryOptionsCommand extends StandardCommand<Void, String> { @Getter private final String value; @Builder public TestEventWithQueryOptionsCommand(HybridTimestamp timestamp, String value) { super(timestamp); this.value = value == null ? "test" : value; } @Override public EventStream<Void> events() { return EventStream.of(TestEventWithQueryOptions.builder().string(value).build()); } @Override public String result() { return "hello, world"; } } @Test @SneakyThrows public void queryOptionsInIndices() { IndexedCollection<EntityHandle<TestEventWithQueryOptions>> coll = indexEngine .getIndexedCollection(TestEventWithQueryOptions.class); coll.clear(); repository.publish(TestEventWithQueryOptionsCommand.builder().build()).get(); assertTrue(coll.retrieve(equal(TestEventWithQueryOptions.ATTR, "test")).isNotEmpty()); } @Test @SneakyThrows public void publishingNewCommand() { assertFalse(repository.getCommands().contains(BogusCommand.class)); repository.addCommandSetProvider( new PackageCommandSetProvider(new Package[] { BogusCommand.class.getPackage() })); repository .addEventSetProvider(new PackageEventSetProvider(new Package[] { BogusEvent.class.getPackage() })); assertTrue(repository.getCommands().contains(BogusCommand.class)); assertTrue(repository.getEvents().contains(BogusEvent.class)); Assert.assertEquals("bogus", repository.publish(BogusCommand.builder().build()).get()); // testing its indexing IndexedCollection<EntityHandle<BogusEvent>> coll = indexEngine.getIndexedCollection(BogusEvent.class); assertTrue(coll.retrieve(equal(BogusEvent.ATTR, "bogus")).isNotEmpty()); assertTrue(coll.retrieve(contains(BogusEvent.ATTR, "us")).isNotEmpty()); Assert.assertEquals(coll.retrieve(equal(BogusEvent.ATTR, "bogus")).uniqueResult().get().string(), "bogus"); } public static class LockCommand extends StandardCommand<Void, Void> { @Builder public LockCommand(HybridTimestamp timestamp) { super(timestamp); } @Override public EventStream<Void> events(Repository repository, LockProvider lockProvider) { lockProvider.lock("LOCK"); return EventStream.empty(); } } @Test(timeOut = 1000) @SneakyThrows public void lockTracking() { repository.publish(LockCommand.builder().build()).get(); Lock lock = lockProvider.lock("LOCK"); assertTrue(lock.isLocked()); lock.unlock(); } public static class ExceptionalLockCommand extends StandardCommand<Void, Void> { @Builder public ExceptionalLockCommand(HybridTimestamp timestamp) { super(timestamp); } @Override public EventStream<Void> events(Repository repository, LockProvider lockProvider) { lockProvider.lock("LOCK"); throw new IllegalStateException(); } } @Test(timeOut = 1000) @SneakyThrows public void exceptionalLockTracking() { repository.publish(ExceptionalLockCommand.builder().build()).exceptionally(throwable -> null).get(); Lock lock = lockProvider.lock("LOCK"); assertTrue(lock.isLocked()); lock.unlock(); } public static class ExceptionalCommand extends StandardCommand<Void, Object> { @Getter private boolean resultCalled = false; @Builder public ExceptionalCommand(HybridTimestamp timestamp) { super(timestamp); } @Override public EventStream<Void> events() { throw new IllegalStateException(); } @Override public Object result() { resultCalled = true; return null; } } @Test @SneakyThrows public void exceptionalCommand() { ExceptionalCommand command = ExceptionalCommand.builder().build(); Object o = repository.publish(command).exceptionally(throwable -> throwable).get(); assertTrue(o instanceof IllegalStateException); Optional<Entity> commandLookup = journal.get(command.uuid()); assertTrue(commandLookup.isPresent()); assertTrue(command.hasTerminatedExceptionally(repository)); // result() was not called assertFalse(command.isResultCalled()); try (ResultSet<EntityHandle<CommandTerminatedExceptionally>> resultSet = repository.query( CommandTerminatedExceptionally.class, and(all(CommandTerminatedExceptionally.class), existsIn(indexEngine.getIndexedCollection(EventCausalityEstablished.class), CommandTerminatedExceptionally.ID, EventCausalityEstablished.EVENT)))) { assertEquals(resultSet.size(), 1); } assertEquals(command.exceptionalTerminationCause(repository).getClassName(), IllegalStateException.class.getName()); try (ResultSet<EntityHandle<JavaExceptionOccurred>> resultSet = repository.query( JavaExceptionOccurred.class, and(all(JavaExceptionOccurred.class), existsIn(indexEngine.getIndexedCollection(EventCausalityEstablished.class), JavaExceptionOccurred.ID, EventCausalityEstablished.EVENT)))) { assertEquals(resultSet.size(), 1); EntityHandle<JavaExceptionOccurred> result = resultSet.uniqueResult(); assertEquals(result.get().getClassName(), IllegalStateException.class.getName()); } } @ToString public static class StreamExceptionCommand extends StandardCommand<Void, Void> { private final UUID eventUUID; @Getter private boolean resultCalled = false; @LayoutConstructor public StreamExceptionCommand(HybridTimestamp timestamp) { super(timestamp); eventUUID = null; } @Builder public StreamExceptionCommand(HybridTimestamp timestamp, UUID eventUUID) { super(timestamp); this.eventUUID = eventUUID == null ? UUID.randomUUID() : eventUUID; } @Override public EventStream<Void> events() { return EventStream.of(Stream.concat( Stream.of(TestEvent.builder().string("test").build().uuid(eventUUID)), Stream.generate(() -> { throw new IllegalStateException(); }))); } @Override public Void result() { resultCalled = true; return null; } } @Test @SneakyThrows public void streamExceptionIndexing() { IndexedCollection<EntityHandle<TestEvent>> coll = indexEngine.getIndexedCollection(TestEvent.class); coll.clear(); UUID eventUUID = UUID.randomUUID(); StreamExceptionCommand command = StreamExceptionCommand.builder().eventUUID(eventUUID).build(); CompletableFuture<Void> future = repository.publish(command); while (!future.isDone()) { Thread.sleep(10); } // to avoid throwing an exception assertTrue(future.isCompletedExceptionally()); // result() was not called assertFalse(command.isResultCalled()); try (ResultSet<EntityHandle<CommandTerminatedExceptionally>> resultSet = repository.query( CommandTerminatedExceptionally.class, and(all(CommandTerminatedExceptionally.class), existsIn(indexEngine.getIndexedCollection(EventCausalityEstablished.class), CommandTerminatedExceptionally.ID, EventCausalityEstablished.EVENT)))) { assertEquals(resultSet.size(), 1); } assertTrue(journal.get(command.uuid()).isPresent()); assertFalse(journal.get(eventUUID).isPresent()); assertTrue(repository.query(TestEvent.class, equal(TestEvent.ATTR, "test")).isEmpty()); } public static class StatePassageCommand extends StandardCommand<String, String> { @Builder public StatePassageCommand(HybridTimestamp timestamp) { super(timestamp); } @Override public EventStream<String> events(Repository repository, LockProvider lockProvider) throws Exception { return EventStream.empty("hello"); } @Override public String result(String state) { return state; } } @Test @SneakyThrows public void statePassage() { String s = repository.publish(StatePassageCommand.builder().build()).get(); assertEquals(s, "hello"); } public static class PassingLockProvider extends StandardCommand<Boolean, Boolean> { @Builder public PassingLockProvider(HybridTimestamp timestamp) { super(timestamp); } @Override public EventStream<Boolean> events(Repository repository, LockProvider lockProvider) throws Exception { return EventStream.empty(lockProvider != null); } @Override public Boolean result(Boolean passed) { return passed; } } @Test @SneakyThrows public void passingLock() { assertTrue(repository.publish(PassingLockProvider.builder().build()).get()); } @Accessors(fluent = true) @ToString public static class TestOptionalEvent extends StandardEvent { @Getter private final Optional<String> optional; @Index({ EQ, UNIQUE }) public final static SimpleIndex<TestOptionalEvent, UUID> ATTR = SimpleIndex.as(StandardEntity::uuid); @Builder public TestOptionalEvent(Optional<String> optional) { this.optional = optional; } } @Accessors(fluent = true) @ToString public static class TestOptionalCommand extends StandardCommand<Void, Void> { @Getter private final Optional<String> optional; @Builder public TestOptionalCommand(HybridTimestamp timestamp, Optional<String> optional) { super(timestamp); this.optional = optional; } @Override public EventStream<Void> events() { return EventStream.of(TestOptionalEvent.builder().optional(optional).build()); } @Index({ EQ, UNIQUE }) public final static SimpleIndex<TestOptionalCommand, UUID> ATTR = SimpleIndex.as(StandardEntity::uuid); } @Test @SneakyThrows public void commandGoesThroughLayoutSerialization() { TestOptionalCommand command = TestOptionalCommand.builder().build(); repository.publish(command).get(); TestOptionalCommand test = repository .query(TestOptionalCommand.class, equal(TestOptionalCommand.ATTR, command.uuid())).uniqueResult() .get(); assertFalse(test.optional().isPresent()); } @Test @SneakyThrows public void eventGoesThroughLayoutSerialization() { TestOptionalCommand command = TestOptionalCommand.builder().build(); repository.publish(command).get(); TestOptionalEvent testOptionalEvent = repository .query(TestOptionalEvent.class, all(TestOptionalEvent.class)).uniqueResult().get(); assertFalse(testOptionalEvent.optional().isPresent()); } @Test @SneakyThrows public void causalRelationship() { RepositoryTestCommand command = RepositoryTestCommand.builder().build(); repository.publish(command).get(); try (ResultSet<EntityHandle<EventCausalityEstablished>> resultSet = repository .query(EventCausalityEstablished.class, equal(EventCausalityEstablished.COMMAND, command.uuid()))) { assertEquals(resultSet.size(), 1); } } public static class LongRunningCommandEvents extends StandardCommand<Void, Void> { final CompletableFuture<Void> future = new CompletableFuture<>(); @Override public EventStream<Void> events() throws Exception { future.get(); return EventStream.empty(); } } @Test @SneakyThrows public void longRunningCommandEventsShouldNotBlock() { LongRunningCommandEvents command = new LongRunningCommandEvents(); repository.publish(command); repository.publish(RepositoryTestCommand.builder().value("1").build()).get(5_000, TimeUnit.MILLISECONDS); command.future.complete(null); } public static class LongRunningCommandEventStreamGeneration extends StandardCommand<Void, Void> { final CompletableFuture<Void> future = new CompletableFuture<>(); @Override public EventStream<Void> events() throws Exception { return EventStream.of(Stream.of(1).map(i -> { try { future.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } return TestEvent.builder().build(); })); } } @Test @SneakyThrows public void longRunningCommandEventStreamGenerationShouldNotBlock() { LongRunningCommandEventStreamGeneration command = new LongRunningCommandEventStreamGeneration(); repository.publish(command); repository.publish(RepositoryTestCommand.builder().value("1").build()).get(5_000, TimeUnit.MILLISECONDS); command.future.complete(null); } @Test @SneakyThrows public void longRunningCommandEventStreamGenerationSameCommandShouldNotBlock() { LongRunningCommandEventStreamGeneration cmd1 = new LongRunningCommandEventStreamGeneration(); repository.publish(cmd1); LongRunningCommandEventStreamGeneration cmd2 = new LongRunningCommandEventStreamGeneration(); cmd2.future.complete(null); repository.publish(cmd2).get(5_000, TimeUnit.MILLISECONDS); cmd1.future.complete(null); } @Accessors(fluent = true) class StickyTimeProvider extends AbstractService implements PhysicalTimeProvider { private final PhysicalTimeProvider timeProvider; StickyTimeProvider(PhysicalTimeProvider timeProvider) { this.timeProvider = timeProvider; } @Setter private boolean sticky = false; private Long lastValue = null; @Override public long getPhysicalTime() { if (sticky && lastValue == null) { lastValue = timeProvider.getPhysicalTime(); } if (sticky) { return lastValue; } return timeProvider.getPhysicalTime(); } @Override protected void doStart() { timeProvider.startAsync().awaitRunning(); notifyStarted(); } @Override protected void doStop() { timeProvider.stopAsync().awaitTerminated(); notifyStopped(); } } @Test @SneakyThrows public void repositoryTimestamp() { StandardRepository newRepository = new StandardRepository(); StickyTimeProvider newTimeProvider = new StickyTimeProvider(new NTPServerTimeProvider()); newTimeProvider.startAsync().awaitRunning(); newTimeProvider.sticky(true); newRepository.setPhysicalTimeProvider(newTimeProvider); HybridTimestamp hybridTimestamp = new HybridTimestamp(newTimeProvider); hybridTimestamp.update(); Journal newJournal = createJournal(); newJournal.setRepository(newRepository); newJournal.getProperties().setRepositoryTimestamp(hybridTimestamp); newRepository.setJournal(newJournal); IndexEngine newIndexEngine = createIndexEngine(); newRepository.setIndexEngine(newIndexEngine); LocalLockProvider newLockProvider = new LocalLockProvider(); newRepository.setLockProvider(newLockProvider); newRepository.startAsync().awaitRunning(); // Because the time is not advancing, the logical counter is // this proves that the saved timestamp was used assertEquals(newRepository.getTimestamp().getLogicalTime(), hybridTimestamp.getLogicalTime()); assertTrue(newRepository.getTimestamp().getLogicalCounter() > hybridTimestamp.getLogicalCounter()); // This tests that the repository saves the timestamp back assertEquals(newJournal.getProperties().getRepositoryTimestamp().get(), newRepository.getTimestamp()); newRepository.stopAsync().awaitTerminated(); newTimeProvider.stopAsync().awaitTerminated(); } }