GH-806 - Add archive support for JPA.
Co-authored-by: Oliver Drotbohm <oliver.drotbohm@broadcom.com>
This commit is contained in:
committed by
Oliver Drotbohm
parent
dc1148624b
commit
6a85fb3350
@@ -0,0 +1,34 @@
|
||||
package org.springframework.modulith.events.jpa;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* JPA entity to represent archived event publications.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "EVENT_PUBLICATION_ARCHIVE")
|
||||
class ArchivedJpaEventPublication extends JpaEventPublication {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ArchivedJpaEventPublication} for the given publication date, listener id, serialized event and
|
||||
* event type.
|
||||
*
|
||||
* @param id
|
||||
* @param publicationDate must not be {@literal null}.
|
||||
* @param listenerId must not be {@literal null} or empty.
|
||||
* @param serializedEvent must not be {@literal null} or empty.
|
||||
* @param eventType must not be {@literal null}.
|
||||
*/
|
||||
public ArchivedJpaEventPublication(UUID id, Instant publicationDate, String listenerId, String serializedEvent,
|
||||
Class<?> eventType) {
|
||||
super(id, publicationDate, listenerId, serializedEvent, eventType);
|
||||
}
|
||||
|
||||
public ArchivedJpaEventPublication() {}
|
||||
}
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package org.springframework.modulith.events.jpa;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
@@ -34,6 +31,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "EVENT_PUBLICATION")
|
||||
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
|
||||
class JpaEventPublication {
|
||||
|
||||
final @Id @Column(length = 16) UUID id;
|
||||
@@ -68,7 +66,7 @@ class JpaEventPublication {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
JpaEventPublication() {
|
||||
protected JpaEventPublication() {
|
||||
|
||||
this.id = null;
|
||||
this.publicationDate = null;
|
||||
@@ -77,9 +75,11 @@ class JpaEventPublication {
|
||||
this.eventType = null;
|
||||
}
|
||||
|
||||
JpaEventPublication markCompleted() {
|
||||
ArchivedJpaEventPublication archive(Instant instant) {
|
||||
|
||||
this.completionDate = Instant.now();
|
||||
return this;
|
||||
var result = new ArchivedJpaEventPublication(id, publicationDate, listenerId, serializedEvent, eventType);
|
||||
result.completionDate = instant;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Drotbohm
|
||||
* @author Dmitry Belyaev
|
||||
* @author Björn Kieling
|
||||
* @author Cora Iberkleid
|
||||
*/
|
||||
@Transactional
|
||||
class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
@@ -53,7 +54,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
|
||||
private static String COMPLETE = """
|
||||
select p
|
||||
from JpaEventPublication p
|
||||
from %s p
|
||||
where
|
||||
p.completionDate is not null
|
||||
order by
|
||||
@@ -113,14 +114,14 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
|
||||
private static final String DELETE_COMPLETED = """
|
||||
delete
|
||||
from JpaEventPublication p
|
||||
from %s p
|
||||
where
|
||||
p.completionDate is not null
|
||||
""";
|
||||
|
||||
private static final String DELETE_COMPLETED_BEFORE = """
|
||||
delete
|
||||
from JpaEventPublication p
|
||||
from %s p
|
||||
where
|
||||
p.completionDate < ?1
|
||||
""";
|
||||
@@ -131,6 +132,8 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
private final EventSerializer serializer;
|
||||
private final CompletionMode completionMode;
|
||||
|
||||
private final String getCompleted, deleteCompleted, deleteCompletedBefore;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JpaEventPublicationRepository} for the given {@link EntityManager} and
|
||||
* {@link EventSerializer}.
|
||||
@@ -148,7 +151,15 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
this.entityManager = entityManager;
|
||||
this.serializer = serializer;
|
||||
this.completionMode = completionMode;
|
||||
}
|
||||
|
||||
var archiveEntityName = completionMode == CompletionMode.ARCHIVE
|
||||
? ArchivedJpaEventPublication.class.getSimpleName()
|
||||
: JpaEventPublication.class.getSimpleName();
|
||||
|
||||
this.getCompleted = COMPLETE.formatted(archiveEntityName);
|
||||
this.deleteCompleted = DELETE_COMPLETED.formatted(archiveEntityName);
|
||||
this.deleteCompletedBefore = DELETE_COMPLETED_BEFORE.formatted(archiveEntityName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -179,6 +190,18 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
.setParameter(2, identifierValue)
|
||||
.executeUpdate();
|
||||
|
||||
} else if (completionMode == CompletionMode.ARCHIVE) {
|
||||
|
||||
var publication = entityManager.createQuery(BY_EVENT_AND_LISTENER_ID, JpaEventPublication.class)
|
||||
.setParameter(1, serializedEvent)
|
||||
.setParameter(2, identifierValue)
|
||||
.getSingleResult();
|
||||
|
||||
var archived = publication.archive(completionDate);
|
||||
|
||||
entityManager.remove(publication);
|
||||
entityManager.persist(archived);
|
||||
|
||||
} else {
|
||||
|
||||
entityManager.createQuery(MARK_COMPLETED_BY_EVENT_AND_LISTENER_ID)
|
||||
@@ -202,6 +225,15 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
.setParameter(1, identifier)
|
||||
.executeUpdate();
|
||||
|
||||
} else if (completionMode == CompletionMode.ARCHIVE) {
|
||||
|
||||
var publication = entityManager.find(JpaEventPublication.class, identifier);
|
||||
|
||||
var archived = publication.archive(completionDate);
|
||||
|
||||
entityManager.remove(publication);
|
||||
entityManager.persist(archived);
|
||||
|
||||
} else {
|
||||
|
||||
entityManager.createQuery(MARK_COMPLETED_BY_ID)
|
||||
@@ -260,7 +292,11 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
@Override
|
||||
public List<TargetEventPublication> findCompletedPublications() {
|
||||
|
||||
return entityManager.createQuery(COMPLETE, JpaEventPublication.class)
|
||||
var type = completionMode == CompletionMode.ARCHIVE
|
||||
? ArchivedJpaEventPublication.class
|
||||
: JpaEventPublication.class;
|
||||
|
||||
return entityManager.createQuery(getCompleted, type)
|
||||
.getResultList()
|
||||
.stream()
|
||||
.map(this::entityToDomain)
|
||||
@@ -285,7 +321,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
*/
|
||||
@Override
|
||||
public void deleteCompletedPublications() {
|
||||
entityManager.createQuery(DELETE_COMPLETED).executeUpdate();
|
||||
entityManager.createQuery(deleteCompleted).executeUpdate();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -297,7 +333,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
|
||||
Assert.notNull(instant, "Instant must not be null!");
|
||||
|
||||
entityManager.createQuery(DELETE_COMPLETED_BEFORE)
|
||||
entityManager.createQuery(deleteCompletedBefore)
|
||||
.setParameter(1, instant)
|
||||
.executeUpdate();
|
||||
}
|
||||
@@ -341,6 +377,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
|
||||
private final JpaEventPublication publication;
|
||||
private final EventSerializer serializer;
|
||||
private Object deserializedEvent;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JpaEventPublicationAdapter} for the given {@link JpaEventPublication} and
|
||||
@@ -373,7 +410,12 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
|
||||
*/
|
||||
@Override
|
||||
public Object getEvent() {
|
||||
return serializer.deserialize(publication.serializedEvent, publication.eventType);
|
||||
|
||||
if (deserializedEvent == null) {
|
||||
this.deserializedEvent = serializer.deserialize(publication.serializedEvent, publication.eventType);
|
||||
}
|
||||
|
||||
return deserializedEvent;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -61,9 +61,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Oliver Drotbohm
|
||||
* @author Dmitry Belyaev
|
||||
* @author Björn Kieling
|
||||
* @author Cora Iberkleid
|
||||
*/
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
class JpaEventPublicationRepositoryIntegrationTests {
|
||||
|
||||
private static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener");
|
||||
@@ -112,218 +111,278 @@ class JpaEventPublicationRepositoryIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired JpaEventPublicationRepository repository;
|
||||
@Autowired EntityManager em;
|
||||
@Autowired Environment environment;
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
@ContextConfiguration(classes = TestConfig.class)
|
||||
static abstract class TestBase {
|
||||
|
||||
CompletionMode completionMode;
|
||||
@Autowired JpaEventPublicationRepository repository;
|
||||
@Autowired EntityManager em;
|
||||
@Autowired Environment environment;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
this.completionMode = environment.getProperty(CompletionMode.PROPERTY, CompletionMode.class);
|
||||
}
|
||||
CompletionMode completionMode;
|
||||
|
||||
@AfterEach
|
||||
public void flush() {
|
||||
em.flush();
|
||||
}
|
||||
@BeforeEach
|
||||
void init() {
|
||||
this.completionMode = environment.getProperty(CompletionMode.PROPERTY, CompletionMode.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsJpaEventPublication() {
|
||||
@AfterEach
|
||||
public void flush() {
|
||||
em.flush();
|
||||
}
|
||||
|
||||
var testEvent = new TestEvent("abc");
|
||||
var serializedEvent = "{\"eventId\":\"abc\"}";
|
||||
@Test
|
||||
void persistsJpaEventPublication() {
|
||||
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||
var testEvent = new TestEvent("abc");
|
||||
var serializedEvent = "{\"eventId\":\"abc\"}";
|
||||
|
||||
var publication = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER));
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||
|
||||
var eventPublications = repository.findIncompletePublications();
|
||||
var publication = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER));
|
||||
|
||||
assertThat(eventPublications).hasSize(1);
|
||||
assertThat(eventPublications.get(0).getEvent()).isEqualTo(publication.getEvent());
|
||||
assertThat(eventPublications.get(0).getTargetIdentifier()).isEqualTo(publication.getTargetIdentifier());
|
||||
assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER))
|
||||
.isPresent();
|
||||
var eventPublications = repository.findIncompletePublications();
|
||||
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
assertThat(eventPublications).hasSize(1);
|
||||
assertThat(eventPublications.get(0).getEvent()).isEqualTo(publication.getEvent());
|
||||
assertThat(eventPublications.get(0).getTargetIdentifier()).isEqualTo(publication.getTargetIdentifier());
|
||||
assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER))
|
||||
.isPresent();
|
||||
|
||||
assertThat(repository.findIncompletePublications()).isEmpty();
|
||||
}
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
@Test // GH-25
|
||||
void shouldTolerateEmptyResult() {
|
||||
|
||||
var testEvent = new TestEvent("id");
|
||||
var serializedEvent = "{\"eventId\":\"id\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
|
||||
assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER)).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-25
|
||||
void shouldNotReturnCompletedEvents() {
|
||||
|
||||
TestEvent testEvent = new TestEvent("abc");
|
||||
String serializedEvent = "{\"eventId\":\"abc\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||
|
||||
var publication = TargetEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||
|
||||
repository.create(publication);
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
var actual = repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER);
|
||||
|
||||
assertThat(actual).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-20
|
||||
void shouldDeleteCompletedEvents() {
|
||||
|
||||
var testEvent1 = new TestEvent("abc");
|
||||
var serializedEvent1 = "{\"eventId\":\"abc\"}";
|
||||
var testEvent2 = new TestEvent("def");
|
||||
var serializedEvent2 = "{\"eventId\":\"def\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent1)).thenReturn(serializedEvent1);
|
||||
when(eventSerializer.deserialize(serializedEvent1, TestEvent.class)).thenReturn(testEvent1);
|
||||
when(eventSerializer.serialize(testEvent2)).thenReturn(serializedEvent2);
|
||||
when(eventSerializer.deserialize(serializedEvent2, TestEvent.class)).thenReturn(testEvent2);
|
||||
|
||||
repository.create(TargetEventPublication.of(testEvent1, TARGET_IDENTIFIER));
|
||||
repository.create(TargetEventPublication.of(testEvent2, TARGET_IDENTIFIER));
|
||||
repository.markCompleted(testEvent1, TARGET_IDENTIFIER, Instant.now());
|
||||
repository.deleteCompletedPublications();
|
||||
|
||||
assertThat(em.createQuery("select p from JpaEventPublication p", JpaEventPublication.class).getResultList())
|
||||
.hasSize(1) //
|
||||
.element(0).extracting(it -> it.serializedEvent).isEqualTo(serializedEvent2);
|
||||
}
|
||||
|
||||
@Test // GH-133
|
||||
void returnsOldestIncompletePublicationsFirst() {
|
||||
|
||||
var now = LocalDateTime.now();
|
||||
|
||||
savePublicationAt(now.withHour(3));
|
||||
savePublicationAt(now.withHour(0));
|
||||
savePublicationAt(now.withHour(1));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.isSortedAccordingTo(Comparator.comparing(TargetEventPublication::getPublicationDate));
|
||||
}
|
||||
|
||||
@Test // GH-251
|
||||
void shouldDeleteCompletedEventsBefore() {
|
||||
|
||||
assumeTrue(completionMode == CompletionMode.UPDATE);
|
||||
|
||||
var testEvent1 = new TestEvent("abc");
|
||||
var serializedEvent1 = "{\"eventId\":\"abc\"}";
|
||||
var testEvent2 = new TestEvent("def");
|
||||
var serializedEvent2 = "{\"eventId\":\"def\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent1)).thenReturn(serializedEvent1);
|
||||
when(eventSerializer.deserialize(serializedEvent1, TestEvent.class)).thenReturn(testEvent1);
|
||||
when(eventSerializer.serialize(testEvent2)).thenReturn(serializedEvent2);
|
||||
when(eventSerializer.deserialize(serializedEvent2, TestEvent.class)).thenReturn(testEvent2);
|
||||
|
||||
repository.create(TargetEventPublication.of(testEvent1, TARGET_IDENTIFIER));
|
||||
repository.create(TargetEventPublication.of(testEvent2, TARGET_IDENTIFIER));
|
||||
|
||||
var now = Instant.now();
|
||||
|
||||
repository.markCompleted(testEvent1, TARGET_IDENTIFIER, now.minusSeconds(30));
|
||||
repository.markCompleted(testEvent2, TARGET_IDENTIFIER, now);
|
||||
repository.deleteCompletedPublicationsBefore(now.minusSeconds(15));
|
||||
|
||||
assertThat(em.createQuery("select p from JpaEventPublication p", JpaEventPublication.class).getResultList())
|
||||
.hasSize(1) //
|
||||
.element(0).extracting(it -> it.serializedEvent).isEqualTo(serializedEvent2);
|
||||
}
|
||||
|
||||
@Test // GH-294
|
||||
void deletesPublicationsByIdentifier() {
|
||||
|
||||
var first = createPublication(new TestEvent("first"));
|
||||
var second = createPublication(new TestEvent("second"));
|
||||
|
||||
repository.deletePublications(List.of(first.getIdentifier()));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.hasSize(1)
|
||||
.element(0)
|
||||
.matches(it -> it.getIdentifier().equals(second.getIdentifier()))
|
||||
.matches(it -> it.getEvent().equals(second.getEvent()));
|
||||
}
|
||||
|
||||
@Test // GH-294
|
||||
void findsPublicationsOlderThanReference() throws Exception {
|
||||
|
||||
var first = createPublication(new TestEvent("first"));
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
var now = Instant.now();
|
||||
var second = createPublication(new TestEvent("second"));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.extracting(TargetEventPublication::getIdentifier)
|
||||
.containsExactly(first.getIdentifier(), second.getIdentifier());
|
||||
|
||||
assertThat(repository.findIncompletePublicationsPublishedBefore(now))
|
||||
.hasSize(1)
|
||||
.element(0).extracting(TargetEventPublication::getIdentifier).isEqualTo(first.getIdentifier());
|
||||
}
|
||||
|
||||
@Test // GH-451
|
||||
void findsCompletedPublications() {
|
||||
|
||||
var event = new TestEvent("first");
|
||||
var publication = createPublication(event);
|
||||
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
if (completionMode == CompletionMode.DELETE) {
|
||||
|
||||
assertThat(repository.findCompletedPublications()).isEmpty();
|
||||
assertThat(repository.findIncompletePublications()).isEmpty();
|
||||
}
|
||||
|
||||
} else {
|
||||
@Test // GH-25
|
||||
void shouldTolerateEmptyResult() {
|
||||
|
||||
assertThat(repository.findCompletedPublications())
|
||||
var testEvent = new TestEvent("id");
|
||||
var serializedEvent = "{\"eventId\":\"id\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
|
||||
assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER)).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-25
|
||||
void shouldNotReturnCompletedEvents() {
|
||||
|
||||
TestEvent testEvent = new TestEvent("abc");
|
||||
String serializedEvent = "{\"eventId\":\"abc\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||
|
||||
var publication = TargetEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||
|
||||
repository.create(publication);
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
var actual = repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER);
|
||||
|
||||
assertThat(actual).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-20
|
||||
void shouldDeleteCompletedEvents() {
|
||||
|
||||
var testEvent1 = new TestEvent("abc");
|
||||
var serializedEvent1 = "{\"eventId\":\"abc\"}";
|
||||
var testEvent2 = new TestEvent("def");
|
||||
var serializedEvent2 = "{\"eventId\":\"def\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent1)).thenReturn(serializedEvent1);
|
||||
when(eventSerializer.deserialize(serializedEvent1, TestEvent.class)).thenReturn(testEvent1);
|
||||
when(eventSerializer.serialize(testEvent2)).thenReturn(serializedEvent2);
|
||||
when(eventSerializer.deserialize(serializedEvent2, TestEvent.class)).thenReturn(testEvent2);
|
||||
|
||||
repository.create(TargetEventPublication.of(testEvent1, TARGET_IDENTIFIER));
|
||||
repository.create(TargetEventPublication.of(testEvent2, TARGET_IDENTIFIER));
|
||||
repository.markCompleted(testEvent1, TARGET_IDENTIFIER, Instant.now());
|
||||
repository.deleteCompletedPublications();
|
||||
|
||||
assertThat(em.createQuery("select p from JpaEventPublication p", JpaEventPublication.class).getResultList())
|
||||
.hasSize(1) //
|
||||
.element(0).extracting(it -> it.serializedEvent).isEqualTo(serializedEvent2);
|
||||
|
||||
if (completionMode == CompletionMode.ARCHIVE) {
|
||||
assertThat(em.createQuery("select p from ArchivedJpaEventPublication p", ArchivedJpaEventPublication.class).getResultList())
|
||||
.hasSize(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH-133
|
||||
void returnsOldestIncompletePublicationsFirst() {
|
||||
|
||||
var now = LocalDateTime.now();
|
||||
|
||||
savePublicationAt(now.withHour(3));
|
||||
savePublicationAt(now.withHour(0));
|
||||
savePublicationAt(now.withHour(1));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.isSortedAccordingTo(Comparator.comparing(TargetEventPublication::getPublicationDate));
|
||||
}
|
||||
|
||||
@Test // GH-251
|
||||
void shouldDeleteCompletedEventsBefore() {
|
||||
|
||||
assumeFalse(completionMode == CompletionMode.DELETE);
|
||||
|
||||
var testEvent1 = new TestEvent("abc");
|
||||
var serializedEvent1 = "{\"eventId\":\"abc\"}";
|
||||
var testEvent2 = new TestEvent("def");
|
||||
var serializedEvent2 = "{\"eventId\":\"def\"}";
|
||||
|
||||
when(eventSerializer.serialize(testEvent1)).thenReturn(serializedEvent1);
|
||||
when(eventSerializer.deserialize(serializedEvent1, TestEvent.class)).thenReturn(testEvent1);
|
||||
when(eventSerializer.serialize(testEvent2)).thenReturn(serializedEvent2);
|
||||
when(eventSerializer.deserialize(serializedEvent2, TestEvent.class)).thenReturn(testEvent2);
|
||||
|
||||
repository.create(TargetEventPublication.of(testEvent1, TARGET_IDENTIFIER));
|
||||
repository.create(TargetEventPublication.of(testEvent2, TARGET_IDENTIFIER));
|
||||
|
||||
var now = Instant.now();
|
||||
|
||||
repository.markCompleted(testEvent1, TARGET_IDENTIFIER, now.minusSeconds(30));
|
||||
repository.markCompleted(testEvent2, TARGET_IDENTIFIER, now);
|
||||
|
||||
repository.deleteCompletedPublicationsBefore(now.minusSeconds(15));
|
||||
|
||||
var entityName = completionMode == CompletionMode.ARCHIVE
|
||||
? ArchivedJpaEventPublication.class.getSimpleName()
|
||||
: JpaEventPublication.class.getSimpleName();
|
||||
|
||||
var type = completionMode == CompletionMode.ARCHIVE
|
||||
? ArchivedJpaEventPublication.class
|
||||
: JpaEventPublication.class;
|
||||
|
||||
assertThat(em.createQuery("select p from " + entityName + " p", type).getResultList())
|
||||
.hasSize(1) //
|
||||
.element(0).extracting(it -> it.serializedEvent).isEqualTo(serializedEvent2);
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-294
|
||||
void deletesPublicationsByIdentifier() {
|
||||
|
||||
var first = createPublication(new TestEvent("first"));
|
||||
var second = createPublication(new TestEvent("second"));
|
||||
|
||||
repository.deletePublications(List.of(first.getIdentifier()));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.hasSize(1)
|
||||
.element(0)
|
||||
.extracting(TargetEventPublication::getEvent)
|
||||
.isEqualTo(event);
|
||||
.matches(it -> it.getIdentifier().equals(second.getIdentifier()))
|
||||
.matches(it -> it.getEvent().equals(second.getEvent()));
|
||||
}
|
||||
|
||||
@Test // GH-294
|
||||
void findsPublicationsOlderThanReference() throws Exception {
|
||||
|
||||
var first = createPublication(new TestEvent("first"));
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
var now = Instant.now();
|
||||
var second = createPublication(new TestEvent("second"));
|
||||
|
||||
assertThat(repository.findIncompletePublications())
|
||||
.extracting(TargetEventPublication::getIdentifier)
|
||||
.containsExactly(first.getIdentifier(), second.getIdentifier());
|
||||
|
||||
assertThat(repository.findIncompletePublicationsPublishedBefore(now))
|
||||
.hasSize(1)
|
||||
.element(0).extracting(TargetEventPublication::getIdentifier).isEqualTo(first.getIdentifier());
|
||||
}
|
||||
|
||||
@Test // GH-451
|
||||
void findsCompletedPublications() {
|
||||
|
||||
var event = new TestEvent("first");
|
||||
var publication = createPublication(event);
|
||||
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
if (completionMode == CompletionMode.DELETE) {
|
||||
|
||||
assertThat(repository.findCompletedPublications()).isEmpty();
|
||||
assertThat(repository.findIncompletePublications()).isEmpty();
|
||||
|
||||
} else {
|
||||
|
||||
assertThat(repository.findCompletedPublications())
|
||||
.hasSize(1)
|
||||
.element(0)
|
||||
.extracting(TargetEventPublication::getEvent)
|
||||
.isEqualTo(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH 806
|
||||
void archivesByEvent() {
|
||||
|
||||
assumeTrue(completionMode == CompletionMode.ARCHIVE);
|
||||
|
||||
var event = new TestEvent("abc");
|
||||
var publication = createPublication(event);
|
||||
|
||||
repository.markCompleted(publication, Instant.now());
|
||||
|
||||
assertThat(repository.findCompletedPublications())
|
||||
.hasSize(1);
|
||||
|
||||
assertThat(em.createQuery("select p from JpaEventPublication p", JpaEventPublication.class).getResultList())
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
@Test // GH 806
|
||||
void archivesById() {
|
||||
|
||||
assumeTrue(completionMode == CompletionMode.ARCHIVE);
|
||||
|
||||
var event = new TestEvent("abc");
|
||||
var publication = createPublication(event);
|
||||
|
||||
repository.markCompleted(publication.getIdentifier(), Instant.now());
|
||||
|
||||
assertThat(repository.findCompletedPublications())
|
||||
.hasSize(1);
|
||||
|
||||
assertThat(em.createQuery("select p from JpaEventPublication p", JpaEventPublication.class).getResultList())
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
private TargetEventPublication createPublication(Object event) {
|
||||
|
||||
var token = event.toString();
|
||||
|
||||
doReturn(token).when(eventSerializer).serialize(event);
|
||||
doReturn(event).when(eventSerializer).deserialize(token, event.getClass());
|
||||
|
||||
return repository.create(TargetEventPublication.of(event, TARGET_IDENTIFIER));
|
||||
}
|
||||
|
||||
private void savePublicationAt(LocalDateTime date) {
|
||||
em.persist(new JpaEventPublication(UUID.randomUUID(), date.toInstant(ZoneOffset.UTC), "", "", Object.class));
|
||||
}
|
||||
|
||||
private record TestEvent(String eventId) {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@ContextConfiguration(classes = TestConfig.class)
|
||||
class WithUpdateCompletionTests extends TestBase {}
|
||||
|
||||
@Nested
|
||||
@TestPropertySource(properties = CompletionMode.PROPERTY + "=DELETE")
|
||||
static class WithDeleteCompletionTests extends JpaEventPublicationRepositoryIntegrationTests {
|
||||
class WithDeleteCompletionTests extends TestBase {}
|
||||
|
||||
}
|
||||
|
||||
private TargetEventPublication createPublication(Object event) {
|
||||
|
||||
var token = event.toString();
|
||||
|
||||
doReturn(token).when(eventSerializer).serialize(event);
|
||||
doReturn(event).when(eventSerializer).deserialize(token, event.getClass());
|
||||
|
||||
return repository.create(TargetEventPublication.of(event, TARGET_IDENTIFIER));
|
||||
}
|
||||
|
||||
private void savePublicationAt(LocalDateTime date) {
|
||||
em.persist(new JpaEventPublication(UUID.randomUUID(), date.toInstant(ZoneOffset.UTC), "", "", Object.class));
|
||||
}
|
||||
|
||||
private record TestEvent(String eventId) {}
|
||||
@Nested
|
||||
@TestPropertySource(properties = CompletionMode.PROPERTY + "=ARCHIVE")
|
||||
class WithArchiveCompletionTests extends TestBase {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user