From 106376e785890cdd3a521433b33b0273f7841189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 14:31:55 +0200 Subject: [PATCH 1/7] refactor: journal events foundation --- .../mongodb/sync/MongoDBSyncAuditStore.java | 20 + .../internal/MongoDBSyncAuditPersistence.java | 8 + .../sync/internal/MongoDBSyncEventStore.java | 142 ++++ .../MongoDBSyncEventStoreE2ETest.java | 213 +++++ .../common/core/journal/JournalEvent.java | 121 +++ .../common/core/journal/JournalEventType.java | 21 + .../community/CommunityAuditPersistence.java | 3 +- .../core/external/store/event/EventStore.java | 50 ++ ...perational-state-from-centralized-audit.md | 744 ++++++++++++++++++ flamingock-gradle-plugin/build.gradle.kts | 8 + .../YamlTemplateInputsConfiguratorTest.kt | 49 +- prompt.txt | 115 +++ .../MongoDBReactiveCollectionHelper.java | 16 +- .../mongodb/MongoDBSyncCollectionHelper.java | 14 +- .../common/mongodb/CollectionHelper.java | 19 +- .../mongodb/CollectionInitializator.java | 131 ++- .../common/mongodb/IndexDefinition.java | 84 ++ .../common/mongodb/MongoDBEventMapper.java | 91 +++ .../mongodb/event/EventFieldConstants.java | 37 + .../event/EventPersistenceConstants.java | 28 + .../mongodb/MongoDBEventMapperTest.java | 109 +++ 21 files changed, 1981 insertions(+), 42 deletions(-) create mode 100644 community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java create mode 100644 community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEventType.java create mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java create mode 100644 docs/ADR-0001-separate-local-operational-state-from-centralized-audit.md create mode 100644 prompt.txt create mode 100644 utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/IndexDefinition.java create mode 100644 utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java create mode 100644 utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java create mode 100644 utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java create mode 100644 utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java index bdd5bf7d1..1fdc67eec 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java @@ -32,6 +32,7 @@ import io.flamingock.store.mongodb.sync.internal.MongoDBSyncLockService; import io.flamingock.externalsystem.mongodb.api.MongoDBExternalSystem; +import static io.flamingock.internal.common.mongodb.event.EventPersistenceConstants.DEFAULT_EVENTS_STORE_NAME; import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME; import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_LOCK_STORE_NAME; @@ -46,6 +47,7 @@ public class MongoDBSyncAuditStore implements CommunityAuditStore { private MongoDatabase database; private String auditRepositoryName = DEFAULT_AUDIT_STORE_NAME; private String lockRepositoryName = DEFAULT_LOCK_STORE_NAME; + private String eventsRepositoryName = DEFAULT_EVENTS_STORE_NAME; private ReadConcern readConcern = ReadConcern.MAJORITY; private ReadPreference readPreference = ReadPreference.primary(); private WriteConcern writeConcern = WriteConcern.MAJORITY.withJournal(true); @@ -85,6 +87,11 @@ public MongoDBSyncAuditStore withLockRepositoryName(String lockRepositoryName) { return this; } + public MongoDBSyncAuditStore withEventsRepositoryName(String eventsRepositoryName) { + this.eventsRepositoryName = eventsRepositoryName; + return this; + } + public MongoDBSyncAuditStore withReadConcern(ReadConcern readConcern) { this.readConcern = readConcern; return this; @@ -120,6 +127,7 @@ public synchronized CommunityAuditPersistence getPersistence() { communityConfiguration, database, auditRepositoryName, + eventsRepositoryName, readConcern, readPreference, writeConcern, @@ -161,10 +169,22 @@ private void validate() { throw new FlamingockException("The 'lockRepositoryName' property is required."); } + if (eventsRepositoryName == null || eventsRepositoryName.trim().isEmpty()) { + throw new FlamingockException("The 'eventsRepositoryName' property is required."); + } + if (auditRepositoryName.trim().equalsIgnoreCase(lockRepositoryName.trim())) { throw new FlamingockException("The 'auditRepositoryName' and 'lockRepositoryName' properties must not be the same."); } + if (eventsRepositoryName.trim().equalsIgnoreCase(auditRepositoryName.trim())) { + throw new FlamingockException("The 'eventsRepositoryName' and 'auditRepositoryName' properties must not be the same."); + } + + if (eventsRepositoryName.trim().equalsIgnoreCase(lockRepositoryName.trim())) { + throw new FlamingockException("The 'eventsRepositoryName' and 'lockRepositoryName' properties must not be the same."); + } + if (readConcern == null) { throw new FlamingockException("The 'readConcern' property is required."); } diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java index 0a355ed8a..d3bd1d0d8 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java @@ -23,6 +23,7 @@ import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.core.configuration.community.CommunityConfigurable; import io.flamingock.internal.core.external.store.audit.community.AbstractCommunityAuditPersistence; +import io.flamingock.internal.core.external.store.event.EventStore; import io.flamingock.internal.util.Result; import io.flamingock.internal.util.id.RunnerId; @@ -34,8 +35,10 @@ public class MongoDBSyncAuditPersistence extends AbstractCommunityAuditPersistence { private MongoDBSyncAuditor auditor; + private MongoDBSyncEventStore eventStore; private final MongoDatabase database; private final String auditCollectionName; + private final String eventsCollectionName; private final ReadConcern readConcern; private final ReadPreference readPreference; private final WriteConcern writeConcern; @@ -45,6 +48,7 @@ public class MongoDBSyncAuditPersistence extends AbstractCommunityAuditPersisten public MongoDBSyncAuditPersistence(CommunityConfigurable localConfiguration, MongoDatabase database, String auditCollectionName, + String eventsCollectionName, ReadConcern readConcern, ReadPreference readPreference, WriteConcern writeConcern, @@ -52,6 +56,7 @@ public MongoDBSyncAuditPersistence(CommunityConfigurable localConfiguration, super(localConfiguration); this.database = database; this.auditCollectionName = auditCollectionName; + this.eventsCollectionName = eventsCollectionName; this.readConcern = readConcern; this.readPreference = readPreference; this.writeConcern = writeConcern; @@ -63,6 +68,9 @@ protected void doInitialize(RunnerId runnerId) { //Auditor auditor = new MongoDBSyncAuditor(database, auditCollectionName, readConcern, readPreference, writeConcern); auditor.initialize(autoCreate); + //Event buffer + eventStore = new MongoDBSyncEventStore(database, eventsCollectionName, readConcern, readPreference, writeConcern); + eventStore.initialize(autoCreate); } @Deprecated diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java new file mode 100644 index 000000000..187c1f97b --- /dev/null +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java @@ -0,0 +1,142 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.store.mongodb.sync.internal; + +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Sorts; +import com.mongodb.client.model.Updates; +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.journal.JournalEvent; +import io.flamingock.internal.common.mongodb.CollectionInitializator; +import io.flamingock.internal.common.mongodb.IndexDefinition; +import io.flamingock.internal.common.mongodb.MongoDBDocumentHelper; +import io.flamingock.internal.common.mongodb.MongoDBEventMapper; +import io.flamingock.internal.common.mongodb.MongoDBSyncCollectionHelper; +import io.flamingock.internal.core.external.store.event.EventStore; +import org.bson.Document; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_ACKNOWLEDGED; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_ID; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_ID; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_SEQUENCE; + +/** + * MongoDB-sync implementation of the local event buffer ({@code flamingockEvents}). + *

+ * Sibling of {@link MongoDBSyncAuditor}/{@link MongoDBSyncLockService}: it owns its own collection and + * index setup. Phase 1 is read/acknowledge only — event writing (atomic with the state write) is a later phase. + */ +public class MongoDBSyncEventStore implements EventStore { + + static final String UNIQUE_INDEX_NAME = "unique_key_sequence"; + static final String UNACKNOWLEDGED_INDEX_NAME = "unacknowledged_by_key_sequence"; + + private final MongoCollection collection; + private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + + MongoDBSyncEventStore(MongoDatabase database, + String collectionName, + ReadConcern readConcern, + ReadPreference readPreference, + WriteConcern writeConcern) { + this.collection = database.getCollection(collectionName) + .withReadConcern(readConcern) + .withReadPreference(readPreference) + .withWriteConcern(writeConcern); + } + + protected void initialize(boolean autoCreate) { + CollectionInitializator initializer = new CollectionInitializator<>( + new MongoDBSyncCollectionHelper(collection), + () -> new MongoDBDocumentHelper(new Document()), + indexDefinitions()); + if (autoCreate) { + initializer.initialize(); + } else { + initializer.justValidateCollection(); + } + } + + /** + * The two indexes for the event buffer: + *

+ */ + private static List indexDefinitions() { + LinkedHashMap uniqueKeys = new LinkedHashMap<>(); + uniqueKeys.put(KEY_STREAM_ID, 1); + uniqueKeys.put(KEY_STREAM_SEQUENCE, 1); + IndexDefinition uniqueIndex = new IndexDefinition(uniqueKeys, true, null, UNIQUE_INDEX_NAME); + + LinkedHashMap partialKeys = new LinkedHashMap<>(); + partialKeys.put(KEY_ACKNOWLEDGED, 1); + partialKeys.put(KEY_STREAM_ID, 1); + partialKeys.put(KEY_STREAM_SEQUENCE, 1); + Map partialFilter = new LinkedHashMap<>(); + partialFilter.put(KEY_ACKNOWLEDGED, false); + IndexDefinition unacknowledgedIndex = + new IndexDefinition(partialKeys, false, partialFilter, UNACKNOWLEDGED_INDEX_NAME); + + return Arrays.asList(uniqueIndex, unacknowledgedIndex); + } + + @Override + public Optional> getLastEventByStream(String streamId) { + Document document = collection.find(Filters.eq(KEY_STREAM_ID, streamId)) + .sort(Sorts.descending(KEY_STREAM_SEQUENCE)) + .limit(1) + .first(); + return document == null ? Optional.empty() : Optional.of(mapper.fromDocument(document)); + } + + @Override + public List> getUnacknowledgedEvents(int limit) { + List> events = new ArrayList<>(); + collection.find(Filters.eq(KEY_ACKNOWLEDGED, false)) + .sort(Sorts.ascending(KEY_STREAM_ID, KEY_STREAM_SEQUENCE)) + .limit(limit) + .forEach(document -> events.add(mapper.fromDocument(document))); + return events; + } + + @Override + public long acknowledgeEvents(Collection eventIds) { + if (eventIds == null || eventIds.isEmpty()) { + return 0L; + } + return collection.updateMany(Filters.in(KEY_EVENT_ID, eventIds), Updates.set(KEY_ACKNOWLEDGED, true)) + .getModifiedCount(); + } +} diff --git a/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java b/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java new file mode 100644 index 000000000..b94810d22 --- /dev/null +++ b/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java @@ -0,0 +1,213 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.store.mongodb.sync.internal; + +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import io.flamingock.core.kit.audit.AuditEntryTestFactory; +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.audit.AuditTxType; +import io.flamingock.internal.common.core.journal.JournalEvent; +import io.flamingock.internal.common.core.journal.JournalEventType; +import io.flamingock.internal.common.mongodb.MongoDBEventMapper; +import org.bson.Document; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class MongoDBSyncEventStoreE2ETest { + + private static final String DB_NAME = "test"; + private static final String EVENTS_COLLECTION = "flamingockEvents"; + + @Container + public static final MongoDBContainer mongoDBContainer = + new MongoDBContainer(DockerImageName.parse("mongo:6")).withReuse(true); + + private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + + private MongoClient mongoClient; + private MongoDatabase database; + private MongoDBSyncEventStore eventStore; + + @BeforeEach + void setUp() { + mongoClient = MongoClients.create(mongoDBContainer.getConnectionString()); + database = mongoClient.getDatabase(DB_NAME); + eventStore = new MongoDBSyncEventStore( + database, EVENTS_COLLECTION, + ReadConcern.MAJORITY, ReadPreference.primary(), WriteConcern.MAJORITY.withJournal(true)); + eventStore.initialize(true); + } + + @AfterEach + void tearDown() { + database.drop(); + mongoClient.close(); + } + + @Test + @DisplayName("initialize creates the unique and partial-unacknowledged indexes") + void createsExpectedIndexes() { + Map byName = listIndexesByName(); + + Document unique = byName.get(MongoDBSyncEventStore.UNIQUE_INDEX_NAME); + assertNotNull(unique, "unique index missing"); + assertTrue(unique.getBoolean("unique", false), "index should be unique"); + assertEquals(new Document("streamId", 1).append("streamSequence", 1), unique.get("key")); + + Document unacked = byName.get(MongoDBSyncEventStore.UNACKNOWLEDGED_INDEX_NAME); + assertNotNull(unacked, "unacknowledged partial index missing"); + assertFalse(unacked.getBoolean("unique", false), "partial index should not be unique"); + assertEquals(new Document("acknowledged", 1).append("streamId", 1).append("streamSequence", 1), unacked.get("key")); + assertEquals(new Document("acknowledged", false), unacked.get("partialFilterExpression")); + } + + @Test + @DisplayName("initialize is idempotent and does not duplicate or recreate indexes") + void initializeIsIdempotent() { + Map before = listIndexesByName(); + + eventStore.initialize(true); + MongoDBSyncEventStore secondInstance = new MongoDBSyncEventStore( + database, EVENTS_COLLECTION, + ReadConcern.MAJORITY, ReadPreference.primary(), WriteConcern.MAJORITY.withJournal(true)); + secondInstance.initialize(true); + + assertEquals(before.keySet(), listIndexesByName().keySet()); + } + + @Test + @DisplayName("the unique index rejects a second event at the same (streamId, streamSequence)") + void uniqueIndexEnforcesStreamPosition() { + seed(Collections.singletonList(event("evt-A1", "stageA", 1L, false))); + + assertThrows(RuntimeException.class, + () -> seed(Collections.singletonList(event("evt-other", "stageA", 1L, false)))); + } + + @Test + @DisplayName("getLastEventByStream returns the highest streamSequence for the stream") + void getLastEventByStreamReturnsHighestSequence() { + seed(Arrays.asList( + event("evt-A1", "stageA", 1L, true), + event("evt-A2", "stageA", 2L, false), + event("evt-A3", "stageA", 3L, false), + event("evt-B1", "stageB", 1L, false))); + + Optional> last = eventStore.getLastEventByStream("stageA"); + assertTrue(last.isPresent()); + assertEquals("evt-A3", last.get().getEventId()); + assertEquals(3L, last.get().getStreamSequence()); + + assertFalse(eventStore.getLastEventByStream("unknown-stream").isPresent()); + } + + @Test + @DisplayName("getUnacknowledgedEvents returns only unacked, ordered by (streamId, streamSequence), capped by limit") + void getUnacknowledgedEventsOrderedAndLimited() { + seed(Arrays.asList( + event("evt-A1", "stageA", 1L, true), + event("evt-A2", "stageA", 2L, false), + event("evt-A3", "stageA", 3L, false), + event("evt-B1", "stageB", 1L, false))); + + List all = ids(eventStore.getUnacknowledgedEvents(10)); + assertEquals(Arrays.asList("evt-A2", "evt-A3", "evt-B1"), all); + + List firstTwo = ids(eventStore.getUnacknowledgedEvents(2)); + assertEquals(Arrays.asList("evt-A2", "evt-A3"), firstTwo); + } + + @Test + @DisplayName("acknowledgeEvents flips the matching events and removes them from the unacknowledged batch") + void acknowledgeEventsRemovesFromBatch() { + seed(Arrays.asList( + event("evt-A2", "stageA", 2L, false), + event("evt-A3", "stageA", 3L, false), + event("evt-B1", "stageB", 1L, false))); + + long modified = eventStore.acknowledgeEvents(Arrays.asList("evt-A2", "evt-B1")); + assertEquals(2L, modified); + + assertEquals(Collections.singletonList("evt-A3"), ids(eventStore.getUnacknowledgedEvents(10))); + + assertEquals(0L, eventStore.acknowledgeEvents(Collections.emptyList())); + } + + // ----------------------------- helpers ----------------------------- + + private Map listIndexesByName() { + return database.getCollection(EVENTS_COLLECTION).listIndexes() + .into(new ArrayList<>()) + .stream() + .filter(index -> index.getString("name") != null) + .collect(Collectors.toMap(index -> index.getString("name"), index -> index)); + } + + private void seed(List> events) { + MongoCollection collection = database.getCollection(EVENTS_COLLECTION); + List documents = events.stream().map(mapper::toDocument).collect(Collectors.toList()); + collection.insertMany(documents); + } + + private static List ids(List> events) { + return events.stream().map(JournalEvent::getEventId).collect(Collectors.toList()); + } + + private static JournalEvent event(String eventId, String streamId, long sequence, boolean acknowledged) { + return new JournalEvent<>( + eventId, + JournalEventType.CHANGE_STATE, + JournalEvent.DEFAULT_VERSION, + streamId, + sequence, + Instant.now(), + auditEntry(eventId), + acknowledged); + } + + private static AuditEntry auditEntry(String changeId) { + return AuditEntryTestFactory.createTestAuditEntry( + changeId, AuditEntry.Status.APPLIED, AuditTxType.NON_TX, (Class) null); + } +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java new file mode 100644 index 000000000..884c3a52f --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java @@ -0,0 +1,121 @@ + +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.core.journal; + +import java.time.Instant; +import java.util.Objects; + +/** + * An immutable runtime fact produced by the Flamingock client . + *

+ * The outer object is the event; {@code data} is the contained domain object (an audit entry, an + * execution, …). Events are held in the durable local buffer. + *

+ * This is a plain Java 8 POJO with no persistence or serialization annotations — each audit-store + * adapter owns the conversion to its own representation. + */ +public final class JournalEvent { + + public static final int DEFAULT_VERSION = 1; + + private final String eventId; + private final JournalEventType eventType; + private final int eventVersion; + + private final String streamId; + private final long streamSequence; + + private final Instant occurredAt; + private final T data; + + private boolean acknowledged; + + public JournalEvent(String eventId, + JournalEventType eventType, + String streamId, + long streamSequence, + Instant occurredAt, + T data) { + this(eventId, eventType, DEFAULT_VERSION, streamId, streamSequence, occurredAt, data, false); + } + + public JournalEvent(String eventId, + JournalEventType eventType, + int eventVersion, + String streamId, + long streamSequence, + Instant occurredAt, + T data, + boolean acknowledged) { + this.acknowledged = acknowledged; + this.eventId = requireNotBlank(eventId, "eventId"); + this.eventType = Objects.requireNonNull(eventType, "eventType"); + this.streamId = requireNotBlank(streamId, "streamId"); + + if (streamSequence < 1) { + throw new IllegalArgumentException("streamSequence must be greater than zero"); + } + + this.eventVersion = eventVersion; + this.streamSequence = streamSequence; + this.occurredAt = Objects.requireNonNull(occurredAt, "occurredAt must not be null"); + this.data = Objects.requireNonNull(data, "data must not be null"); + } + + public String getEventId() { + return eventId; + } + + public JournalEventType getEventType() { + return eventType; + } + + public int getEventVersion() { + return eventVersion; + } + + public String getStreamId() { + return streamId; + } + + public long getStreamSequence() { + return streamSequence; + } + + public Instant getOccurredAt() { + return occurredAt; + } + + public T getData() { + return data; + } + + public boolean isAcknowledged() { + return acknowledged; + } + + public void acknowledge() { + this.acknowledged = true; + } + + private static String requireNotBlank(String value, String fieldName) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(fieldName + " must not be blank"); + } + return value; + } +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEventType.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEventType.java new file mode 100644 index 000000000..2453bf0f6 --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEventType.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.core.journal; + +public enum JournalEventType { + EXECUTION_STATE, + CHANGE_STATE +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java index aa2ca8451..6508187d3 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java @@ -16,8 +16,7 @@ package io.flamingock.internal.core.external.store.audit.community; import io.flamingock.internal.core.external.store.audit.AuditPersistence; +import io.flamingock.internal.core.external.store.event.EventStore; public interface CommunityAuditPersistence extends AuditPersistence, CommunityAuditReader { - -// LocalLockService getLockService(); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java new file mode 100644 index 000000000..c2c92e342 --- /dev/null +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java @@ -0,0 +1,50 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.core.external.store.event; + +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.journal.JournalEvent; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * Persistence abstraction for the local durable event buffer (see ADR-0001). + *

+ * Each audit store provides its own implementation (MongoDB, SQL, DynamoDB, Couchbase, …) so upstream + * callers depend on this contract rather than a concrete store. The buffer holds immutable facts awaiting + * synchronization to Flamingock Cloud; it is a separate concern from the operational/audit state. + *

+ * Phase 1 exposes read/acknowledge only. Event writing (atomic with the state write) is a later phase. + */ +public interface EventStore { + + /** + * Returns the event with the highest {@code streamSequence} for the given stream, if any. + */ + Optional> getLastEventByStream(String streamId); + + /** + * Returns up to {@code limit} events that have not yet been acknowledged, in best-effort delivery order. + */ + List> getUnacknowledgedEvents(int limit); + + /** + * Marks the events with the given ids as acknowledged and returns how many were updated. + */ + long acknowledgeEvents(Collection eventIds); +} diff --git a/docs/ADR-0001-separate-local-operational-state-from-centralized-audit.md b/docs/ADR-0001-separate-local-operational-state-from-centralized-audit.md new file mode 100644 index 000000000..867d9b514 --- /dev/null +++ b/docs/ADR-0001-separate-local-operational-state-from-centralized-audit.md @@ -0,0 +1,744 @@ +## Current Community approach (at July 2026) + +Flamingock Community stores the complete local change history in an append-oriented ledger, currently represented by `flamingockAuditLog`. + +This local history serves two purposes: + +1. it records the audit history of change executions; +2. Flamingock aggregates that history to reconstruct the current operational state and determine which changes have already been executed. + +Community also exposes basic audit capabilities, including audit access through the CLI. + +## Current intended Cloud approach + +The current Cloud design, which has not yet reached production, moves the authoritative audit log from the customer’s local database to Flamingock Cloud. + +Under that model: + +* the complete audit history is stored in Cloud; +* the local database is no longer the authoritative audit store; +* during application startup, the Flamingock client contacts Cloud; +* Cloud uses the centralized audit history and the changes declared by the client to decide what must execute; +* the client depends on Cloud to reconstruct the execution state and receive the execution plan. + +This makes Flamingock Cloud a synchronous and potentially blocking dependency in the application startup path. + +The CLI can execute changes before application startup, and a self-hosted offering can move the server into the customer’s infrastructure, but neither option fully removes the architectural concern in the normal SaaS model. + +# Motivation for changing the approach + +There are two main motivations. + +## 1. Protect the customer’s critical path + +A customer should not lose the ability to determine what has already been executed merely because Flamingock Cloud is unavailable. + +Making the Cloud service mandatory during application startup is likely to be viewed negatively, particularly by enterprise customers. A temporary outage of a third-party control plane should not prevent: + +* an application restart; +* autoscaling; +* deployment recovery; +* disaster recovery; +* execution of changes that do not require an online governance decision. + +Flamingock Cloud should normally participate as planner, orchestrator and governance authority, but the core operational state required for execution must remain local. + +When Cloud is unavailable, Flamingock should degrade gracefully and preserve the critical path whenever policy permits. + +Some Cloud-governed controls may still intentionally block particular changes. For example, if a production change requires approval and the approval cannot be verified, that change may need to fail closed unless an explicit emergency mechanism is used. That does not justify making Cloud necessary merely to discover the current local execution state. + +## 2. Establish a clear Community/Enterprise boundary + +Previously, the product boundary around audit was unclear: + +* Community had the basic local audit ledger and CLI access; +* Cloud was expected to provide deeper audit, reporting, governance and compliance capabilities. + +The revised approach should establish a clearer line: + +* Community provides reliable local execution and operational state. +* Flamingock Cloud provides the audit product: centralized history, reporting, governance, organizational visibility and compliance capabilities. + +Community still persists all generated events for durability and future portability, but it does not present those events as the supported audit feature. + +Cloud receives those events asynchronously and transforms them into the centralized audit and governance model. + +# Architectural decision + +Flamingock is separating two responsibilities that were previously represented by the same local audit history: + +1. the current operational state needed to execute safely; +2. the historical facts used to build centralized audit and governance capabilities. + +These responsibilities now have different sources of truth, models and lifecycles. + +# 1. Local operational source of truth + +The Flamingock client must retain locally everything required to execute safely and independently of Flamingock Cloud. + +The local operational state is authoritative for questions such as: + +* Which changes have already been applied? +* Which changes remain pending? +* What is the current effective state of a change? +* Does an execution need recovery or retry? +* What should Flamingock execute during application startup? + +This local state protects the customer’s critical path. + +Flamingock Cloud must not be required merely to reconstruct the current operational state of the target system. + +Cloud can still normally: + +* inspect the client state; +* provide the execution plan; +* coordinate work between instances; +* evaluate policies; +* enforce approvals; +* provide other premium governance capabilities. + +When Cloud is unavailable, the client must still know its operational state and be capable of graceful degradation according to the configured governance policy. + +## Reuse of the existing audit storage + +The existing local `audit_log` storage will continue to be used for compatibility and pragmatism, but its primary responsibility changes. + +Its effective responsibility becomes: + +> Store the current effective state required by Flamingock to determine what has been completed and what must be executed. + +For newly produced records, the effective state will generally be updated using CRU semantics: + +* create; +* read; +* update; +* no normal delete. + +The table will no longer accumulate every transition purely to reconstruct the current state. + +Legacy records may still contain multiple entries created by earlier versions. Existing state-reconstruction logic can continue supporting those records. There is no need for a disruptive migration solely to rewrite or remove the historical data. + +The physical table name may remain `audit_log` even though its principal architectural responsibility becomes operational state. + +Do not allow this historical table name to dictate the new domain model. In core code, reason in terms of operational state and current effective change state. + +# 2. Durable local event buffer + +The client will additionally persist every relevant Flamingock event in a durable, append-oriented local event buffer. + +Examples include: + +* execution created; +* execution started; +* execution completed; +* stage started; +* stage completed; +* stage failed; +* stage retried; +* change started; +* change applied; +* change failed; +* change rolled back; +* future governance or execution facts. + +The event buffer and operational state store have separate responsibilities: + +> Operational state answers: “Where are we now, and what should execute?” + +> Events answer: “What happened over time?” + +The event buffer is not used as the source of truth for deciding what must execute. + +It is the durable record of facts waiting to be synchronized and later used by Cloud to construct audit and governance projections. + +Whenever the underlying store permits it, the event and its corresponding operational-state transition should be persisted as part of the same logical atomic operation. + +The client must never forget or clean an event before Flamingock Cloud has durably and idempotently acknowledged it. + +# 3. Community and Enterprise behaviour + +Both Community and Enterprise runtimes generate and persist the same local events. + +## Community + +In Community: + +* the local operational state remains fully functional; +* events remain durably stored in the local event buffer; +* no Cloud consumer is active; +* the event buffer preserves future portability; +* Community does not expose the centralized audit product; +* the retained events can be synchronized if the customer later enables Flamingock Cloud. + +Customers may eventually be allowed to purge retained events, but they must be clearly informed that purged events cannot subsequently be imported into Cloud as historical audit evidence. + +The fact that the events physically exist in the customer’s database does not make the internal event buffer a supported Community audit API. + +The table is internal Flamingock infrastructure. Its schema is managed by Flamingock and should not be treated as a stable public reporting contract. + +## Enterprise and Cloud + +In Enterprise/Cloud: + +* the same events are generated locally; +* they are sent asynchronously to Flamingock Cloud in batches; +* application execution does not wait for every event to be sent; +* temporary Cloud unavailability does not lose events; +* the client retries delivery until the events are acknowledged; +* acknowledged events become eligible for later cleanup according to a configured retention policy. + +Cleanup: + +* is not part of the critical path; +* is not urgent; +* must be separate from acknowledgement; +* must occur only after durable Cloud acceptance. + +Enterprise does not remove a functional Community capability. It adds: + +* centralized ingestion; +* searchable audit history; +* reporting; +* governance; +* approvals; +* policy enforcement; +* organizational visibility; +* compliance capabilities; +* managed retention; +* support and enterprise operations. + +The event buffer is the reliable transport and evidence source. + +Flamingock Cloud is the audit product. + +# 4. Source-of-truth boundaries + +There is deliberately no single source of truth for every concern. + +Use the following terminology: + +> The client is the authoritative source of truth for current operational state and execution. + +> Flamingock Cloud is the authoritative organizational record for centralized audit, governance, reporting and compliance. + +Events originate in the client. + +Before acknowledgement, they are durable local evidence waiting to be delivered. + +Once Cloud has validated and durably accepted them, they become part of the authoritative centralized audit record. + +Conceptually: + +```text +Local operational state + → authoritative for execution and recovery + +Local durable events + → original execution facts waiting for synchronization + +Flamingock Cloud + → authoritative organizational audit and governance record +``` + +# 5. Event production and persistence abstraction + +Event generation and processing logic are centralized in the Flamingock core/client. + +The supported audit-store implementations provide only the persistence abstraction needed to: + +* save an event; +* retrieve a batch of pending events; +* mark events as acknowledged; +* eventually clean acknowledged events. + +The initially supported stores are: + +* SQL databases; +* MongoDB; +* DynamoDB; +* Couchbase. + +The event domain model must remain persistence-agnostic. + +Each adapter decides how to represent the event: + +* SQL rows and serialized event data; +* MongoDB documents; +* DynamoDB items; +* Couchbase documents. + +Do not introduce Jackson, BSON, DynamoDB, Couchbase or any other persistence technology into the event domain class. + +The event class must remain a plain Java 8 domain POJO. + +# 6. Event ingestion API + +The client must not segregate event types into different server endpoints. + +All asynchronous facts generated by the Flamingock runtime should be sent through one generic batched event-ingestion endpoint. + +Conceptually: + +```text +Client event buffer + → generic batch ingestion endpoint + → immutable raw-event persistence + → asynchronous routing and projections +``` + +The client owns: + +* generating events; +* persisting them durably; +* delivering them reliably; +* retrying until acknowledged; +* sending them in the best practical order. + +The client must not know: + +* which Cloud endpoint corresponds to each event subtype; +* which internal projection consumes the event; +* how Cloud stores audit versus execution information; +* how older event versions are transformed; +* how Cloud routes the event internally. + +The server owns: + +* authentication and authorization; +* envelope validation; +* idempotency; +* durable raw-event persistence; +* event-version handling; +* transformation; +* routing; +* projection generation. + +The server acknowledgement means: + +> The event has been accepted idempotently and persisted durably. + +It must not mean that every projection has already completed. + +Commands and queries requiring an immediate business response may continue to use specific typed endpoints. + +The generic event-ingestion endpoint is specifically for asynchronous runtime facts. + +# 7. Raw-event retention and projections in Cloud + +Cloud should persist each immutable raw event as received before deriving projections. + +The raw event is the basis for: + +* replay; +* debugging; +* rebuilding projections; +* audit timelines; +* execution summaries; +* current-state views; +* reporting; +* compliance views; +* future event transformations. + +Cloud projections are eventually consistent. + +The ingestion endpoint should not synchronously perform every projection. + +A suitable conceptual flow is: + +```text +Ingestion endpoint + → idempotent raw-event inbox/store + → durable projection-work signal or server outbox + → asynchronous projection workers +``` + +Cloud should be capable of receiving events out of transport order and later building the correct logical projections. + +# 8. Ordering model + +There is no global total order across all Flamingock events. + +Stages may run in parallel and may eventually be assigned to different application instances. A global monotonically increasing sequence would introduce unnecessary coordination and conflict with that parallelism. + +The client should send pending events in the best practical order, usually approximately oldest first. + +This is a best-effort transport concern. Correctness must not depend on it. + +Events may arrive at Cloud out of order. + +Cloud must handle that. + +## Stage-level sequencing + +Strict sequencing is required within a stage stream. + +The intended stream identity is currently the `stageId`, within the implicit local service and environment context. + +Retries and attempts for the same stage remain in the same stream. Do not include an attempt identifier in the stream identity if doing so would split the required stage-level sequence. + +For each stream: + +* sequence numbers start from an agreed initial value; +* they increase monotonically; +* locally generated sequences must not contain gaps; +* two different events must never occupy the same `(streamId, streamSequence)`; +* retrying delivery of the same event must preserve its event ID and sequence. + +Flamingock already holds at least a stage-level distributed lock. + +Under that lock, the active writer can: + +1. read the latest persisted sequence for the stage; +2. continue incrementing it in memory; +3. generate and persist events sequentially; +4. prevent concurrent writers from creating conflicting positions. + +Cloud may temporarily receive: + +```text +1, 3, 2 +``` + +That is acceptable. + +Cloud must distinguish between: + +* a temporary transport gap; +* an older event arriving after a newer event; +* a permanently missing event; +* an integrity conflict. + +Projection correctness must use the explicit stage-stream sequence rather than Cloud insertion order. + +The event timestamp remains useful for: + +* timelines; +* approximate event ordering; +* selecting best-effort delivery batches. + +It is not the authoritative ordering mechanism within a stage stream. + +# 9. Idempotency + +Assume Cloud ingestion is idempotent. + +Each event has a globally unique opaque event ID used as its idempotency key. + +The event ID must not embed audit, execution, change or other business semantics. + +Business identity belongs in the event data. + +Cloud must accept repeated delivery of the same event without creating duplicate raw facts or projection effects. + +The combination: + +```text +(streamId, streamSequence) +``` + +must represent one logical stream position. + +Receiving the same event again for that position is an idempotent retry. + +Receiving a different event for an already occupied position is an integrity conflict and must not be accepted as another valid event. + +# 10. Event model principles + +The event domain model is generic and persistence-agnostic. + +It conceptually contains: + +* an opaque `eventId`; +* an event type; +* an event-schema version; +* a `streamId`; +* a sequence within that stream; +* the time at which the fact occurred; +* the actual domain data, such as an audit entry or execution object. + +The outer object is the event. + +The contained audit entry, execution or similar domain object is the event data. + +Do not require a generic `executionId` in every event. Event types that require execution identity can carry it in their own domain data. + +Use terminology equivalent to `occurredAt`, rather than `createdAt`, because this represents when the fact occurred. + +Other timestamps have different meanings: + +* locally persisted time; +* Cloud received time; +* Cloud acknowledged time; +* projection processing time. + +The domain event must be a plain Java 8 POJO with no persistence or serialization annotations. + +Each audit-store adapter owns the conversion to its persistence representation. + +# 11. Cloud planning and graceful degradation + +This architectural change does not remove Flamingock Cloud’s role as planner and orchestrator. + +When Cloud is available, the intended normal path may still include: + +* sending the declared changes and relevant local state to Cloud; +* receiving an execution plan; +* assigning stages to instances; +* coordinating parallel stage execution; +* evaluating governance policies; +* checking approvals; +* applying premium controls. + +The important change is that Cloud is no longer required to reconstruct the local operational state. + +If Cloud cannot be reached: + +* the client still knows which changes have been executed; +* the client does not lose operational continuity; +* the execution path can degrade according to policy; +* locally generated events continue to be stored; +* those events are synchronized later. + +Some governance capabilities may intentionally remain unavailable or fail closed while offline. Emergency and break-glass mechanisms will be designed separately. + +# 12. Future cryptographic integrity + +Cryptographic chaining is explicitly outside the MVP and must not be implemented as part of this task. + +However, the initial architecture must preserve the foundations needed to add it later. + +The future direction may include: + +* a Cloud-issued execution anchor; +* an independent cryptographic chain per stage; +* previous-event hashes; +* event hashes; +* forward-secure authentication; +* batched verification by Cloud; +* signed Cloud receipts; +* aggregation of parallel stage heads into an execution-level commitment. + +This future capability is intended to provide tamper-evident, cryptographically verifiable audit evidence for Flamingock-managed changes. + +The MVP should preserve: + +* immutable events; +* opaque and stable event IDs; +* explicit event versions; +* stage-level streams; +* monotonic, gapless stream sequences; +* durable local buffering; +* asynchronous batched delivery; +* idempotent Cloud ingestion; +* immutable raw-event persistence in Cloud. + +Do not add speculative cryptographic fields or implementation complexity unless required by the current implementation. Preserve the architectural extension points and invariants instead. + +# 13. Audit guarantees and scope + +The centralized audit capability records and protects changes managed through Flamingock. + +It must not claim to prove that nobody modified an external system outside Flamingock. + +The honest scope is: + +> Flamingock provides centralized and potentially cryptographically verifiable evidence for Flamingock-managed changes. + +Future capabilities such as: + +* drift detection; +* target-state verification; +* native external-system audit integration; +* out-of-band activity detection; + +may provide additional evidence, but they are separate concerns. + +# 14. Product boundary + +The intended product boundary is: + +```text +Flamingock Community + → reliable local execution + → current operational state + → durable generation and retention of execution events + → future portability to Cloud + +Flamingock Cloud / Enterprise + → centralized audit + → reporting + → governance + → approvals + → policies + → organizational visibility + → compliance capabilities + → enterprise operations and support +``` + +Community storing internal events does not mean that Community provides the audit product. + +Flamingock Cloud converts durable, isolated runtime facts into a centralized, searchable, governed and eventually cryptographically verifiable organizational audit trail. + +# 15. Architectural summary + +The core separation is: + +```text +Local operational core + → current state + → execution + → recovery + → critical-path resilience + +Asynchronous event channel + → durable local facts + → reliable batched synchronization + +Centralized audit and governance layer + → history + → approvals + → policies + → reporting + → compliance + → organizational visibility +``` + +This architecture preserves Flamingock Cloud’s enterprise capabilities while removing the unnecessary requirement for Cloud to be available merely for the client to know its current execution state. + + + + +## Tech notes +### FlamingockEvent + +```java +package io.flamingock.internal.common.core.journal; + + +import java.time.Instant; +import java.util.Objects; + +public final class FlamingockEvent { + + public static final int DEFAULT_VERSION = 1; + + private final String eventId; + private final FlamingockEventType eventType; + private final int eventVersion; + + private final String streamId; + private final long streamSequence; + + private final Instant occurredAt; + private final T data; + + private boolean acknowledged; + + public FlamingockEvent( + String eventId, + FlamingockEventType eventType, + String streamId, + long streamSequence, + Instant occurredAt, + T data) { + this(eventId, eventType, DEFAULT_VERSION, streamId, streamSequence, occurredAt, data, false); + } + + + public FlamingockEvent( + String eventId, + FlamingockEventType eventType, + int eventVersion, + String streamId, + long streamSequence, + Instant occurredAt, + T data, + boolean acknowledged) { + this.acknowledged = acknowledged; + this.eventId = requireNotBlank(eventId, "eventId"); + this.eventType = Objects.requireNonNull(eventType, "eventType"); + + + this.streamId = requireNotBlank(streamId, "streamId"); + + if (streamSequence < 1) { + throw new IllegalArgumentException( + "streamSequence must be greater than zero" + ); + } + + this.eventVersion = eventVersion; + this.streamSequence = streamSequence; + this.occurredAt = Objects.requireNonNull( + occurredAt, + "occurredAt must not be null" + ); + this.data = Objects.requireNonNull( + data, + "data must not be null" + ); + } + + public String getEventId() { + return eventId; + } + + public FlamingockEventType getEventType() { + return eventType; + } + + public int getEventVersion() { + return eventVersion; + } + + public String getStreamId() { + return streamId; + } + + public long getStreamSequence() { + return streamSequence; + } + + public Instant getOccurredAt() { + return occurredAt; + } + + public T getData() { + return data; + } + + public boolean isAcknowledged() { + return acknowledged; + } + + public void acknowledge() { + this.acknowledged = true; + } + + private static String requireNotBlank( + String value, + String fieldName) { + + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException( + fieldName + " must not be blank" + ); + } + + return value; + } + +} + +``` + +### FlamingockEventType + +```java +package io.flamingock.internal.common.core.journal; + +public enum FlamingockEventType { + EXECUTION_STATE, + CHANGE_STATE +} +``` \ No newline at end of file diff --git a/flamingock-gradle-plugin/build.gradle.kts b/flamingock-gradle-plugin/build.gradle.kts index 5b57e735a..873432b12 100644 --- a/flamingock-gradle-plugin/build.gradle.kts +++ b/flamingock-gradle-plugin/build.gradle.kts @@ -43,6 +43,14 @@ java { tasks.test { useJUnitPlatform() + // Give ProjectBuilder-based tests a Gradle user home under build/, outside their @TempDir. + // This keeps Gradle's memory-mapped native .bin files out of the @TempDir so JUnit can + // reliably clean it up on all platforms (on Windows those mapped files stay locked for the + // life of the JVM and would otherwise fail @TempDir deletion). Removed by `clean`. + systemProperty( + "flamingock.test.gradleUserHome", + layout.buildDirectory.dir("test-gradle-home").get().asFile.absolutePath + ) } publishing { diff --git a/flamingock-gradle-plugin/src/test/kotlin/io/flamingock/gradle/internal/YamlTemplateInputsConfiguratorTest.kt b/flamingock-gradle-plugin/src/test/kotlin/io/flamingock/gradle/internal/YamlTemplateInputsConfiguratorTest.kt index 3e1af54de..2997f5ce4 100644 --- a/flamingock-gradle-plugin/src/test/kotlin/io/flamingock/gradle/internal/YamlTemplateInputsConfiguratorTest.kt +++ b/flamingock-gradle-plugin/src/test/kotlin/io/flamingock/gradle/internal/YamlTemplateInputsConfiguratorTest.kt @@ -15,10 +15,13 @@ */ package io.flamingock.gradle.internal +import org.gradle.api.Project import org.gradle.api.tasks.compile.JavaCompile import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.io.TempDir @@ -36,9 +39,45 @@ class YamlTemplateInputsConfiguratorTest { @TempDir lateinit var projectDir: Path + companion object { + /** + * Shared Gradle user home for the ProjectBuilder fixtures, located under the module's + * build directory (supplied via a system property from build.gradle.kts) rather than + * inside each test's [TempDir]. Gradle's native services memory-map files (e.g. + * `.tmp/gradle*.bin`) under this home for the life of the test JVM; on Windows + * those mapped files stay locked and would break JUnit's recursive [TempDir] deletion + * if they lived inside it. Keeping the home outside the [TempDir] lets cleanup succeed + * on every platform. + */ + private lateinit var gradleUserHome: File + + @JvmStatic + @BeforeAll + fun setUpSharedGradleHome() { + val configured = System.getProperty("flamingock.test.gradleUserHome") + ?: File(System.getProperty("java.io.tmpdir"), "flamingock-test-gradle-home").absolutePath + gradleUserHome = File(configured).apply { mkdirs() } + } + + @JvmStatic + @AfterAll + fun tearDownSharedGradleHome() { + // Best-effort: on POSIX the mapped files unlink immediately; on Windows they stay + // locked until the JVM exits, so deletion may fail — that's fine, `clean` removes it. + runCatching { gradleUserHome.deleteRecursively() } + } + } + + /** Builds a ProjectBuilder project whose Gradle user home lives outside the [TempDir]. */ + private fun newProject(): Project = + ProjectBuilder.builder() + .withProjectDir(projectDir.toFile()) + .withGradleUserHomeDir(gradleUserHome) + .build() + @Test fun `attaches YAML inputs to compileJava when java plugin is applied`() { - val project = ProjectBuilder.builder().withProjectDir(projectDir.toFile()).build() + val project = newProject() project.plugins.apply("java") // Fixture: one YAML under src/main/java, one under src/main/resources. @@ -57,7 +96,7 @@ class YamlTemplateInputsConfiguratorTest { @Test fun `attaches YAML inputs to compileTestJava for test source set`() { - val project = ProjectBuilder.builder().withProjectDir(projectDir.toFile()).build() + val project = newProject() project.plugins.apply("java") writeYamlFixture("src/test/java/com/example/changes/_0001__test.yaml") @@ -75,7 +114,7 @@ class YamlTemplateInputsConfiguratorTest { @Test fun `compileJava and compileTestJava only track their own source set's YAML files`() { - val project = ProjectBuilder.builder().withProjectDir(projectDir.toFile()).build() + val project = newProject() project.plugins.apply("java") writeYamlFixture("src/main/resources/_main_only.yaml") @@ -98,7 +137,7 @@ class YamlTemplateInputsConfiguratorTest { @Test fun `is a no-op when java plugin is not applied`() { - val project = ProjectBuilder.builder().withProjectDir(projectDir.toFile()).build() + val project = newProject() // No java plugin applied. assertDoesNotThrow { @@ -108,7 +147,7 @@ class YamlTemplateInputsConfiguratorTest { @Test fun `is a no-op when there are no YAML fixtures`() { - val project = ProjectBuilder.builder().withProjectDir(projectDir.toFile()).build() + val project = newProject() project.plugins.apply("java") assertDoesNotThrow { diff --git a/prompt.txt b/prompt.txt new file mode 100644 index 000000000..e4bfcc998 --- /dev/null +++ b/prompt.txt @@ -0,0 +1,115 @@ +me ha surgido un problema. Resulta que en el sistema ya tenemos el concepto de Eventos. Exactamente flamingock provee unos eventos al usuario: cuando empieza el proceso, por cada stage, cuando finaliza o tiene un error y al final del proceso, entre otros. Son eventos de cara al usuario y ya se estan usando. + +Claro introducir esto tambien como evento puede ser confuso, uanque sea un concepto interno, mayormente, pero podemos aprovchar que todavia no se ha hecho release para intentar buscar una forma limpia y profesional de diferenciarlos. + + +Por una parte, el usuario ya puede estar usando los eventos(los del lifceCycle) , lo digo porque idealmente los hubiera llamado LifecycleEvent, pero se llamo Event. Sin embargo, ese Event es una marker interface, realmente los eventos que se usan son clases concretas. + +package io.flamingock.internal.core.event.model; + +public interface Event { + +} + +Ejemplo del PipelineCompletedEvent +public interface IPipelineCompletedEvent extends Event { + + ExecuteResponseData getResult(); + +} + +public class PipelineCompletedEvent implements IPipelineCompletedEvent { + + private final ExecuteResponseData result; + + public PipelineCompletedEvent(ExecuteResponseData result) { + this.result = result; + } + + @Override + public ExecuteResponseData getResult() { + return result; + } +} + + +public class SpringPipelineCompletedEvent extends ApplicationEvent implements IPipelineCompletedEvent { + + private final IPipelineCompletedEvent event; + + /** + * Create a new {@code ApplicationEvent}. + * + * @param source the object on which the event initially occurred or with + * which the event is associated (never {@code null}) + */ + public SpringPipelineCompletedEvent(Object source, IPipelineCompletedEvent event) { + super(source); + this.event = event; + } + + @Override + public ExecuteResponseData getResult() { + return event.getResult(); + } + + @Override + public String toString() { + return "SpringPipelineCompletedEvent{" + + "event=" + event + + ", source=" + source + + '}'; + } +} + + +Realmente lo que hace le usuario es definir los listeners recibiendo esos Objetos. Ejemplo de uno: + +package io.flamingock.internal.core.event.listener; + +import io.flamingock.internal.common.core.response.data.ExecutionReportFormatter; +import io.flamingock.internal.core.event.model.IPipelineCompletedEvent; +import io.flamingock.internal.util.log.FlamingockLoggerFactory; +import org.slf4j.Logger; + +import java.util.function.Consumer; + +/** + * Writes the canonical execution report at {@code INFO} on the {@code FK-Report} logger when a + * pipeline completes successfully. Registered by the builder unless + * {@code enableDefaultExecutionReport(false)} is set. + * + *

Defensive: must never throw. A failure inside the formatter is reported as a single + * fallback {@code ERROR} line and swallowed so it cannot mask the run outcome. + */ +public final class DefaultPipelineCompletedReportListener implements Consumer { + + // Bare component name; FlamingockLoggerFactory.getLogger prepends "FK-" → "FK-Report". + public static final String LOGGER_NAME = "Report"; + + private static final Logger logger = FlamingockLoggerFactory.getLogger(LOGGER_NAME); + + @Override + public void accept(IPipelineCompletedEvent event) { + try { + String report = ExecutionReportFormatter.report(event != null ? event.getResult() : null); + logger.info("{}{}", System.lineSeparator(), report); + } catch (Throwable t) { + logger.error("FK-Report rendering failed: {}", t.toString(), t); + } + } +} + + +builder.setPipelineCompletedListener(new DefaultPipelineCompletedReportListener ()); + + +Aunque como se ve el usuario no usa el Evetn directamente. Supongo que puede ser seguro renombrarlo a algocomo LifecycleEvent. +Otro enfoque es dejarlo como está y renombrar el event nuevo que hemos creado FlamingockEvent. + +Que opinas? + + +Por cierto hablemos en ingles + + diff --git a/utils/mongodb-reactive-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBReactiveCollectionHelper.java b/utils/mongodb-reactive-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBReactiveCollectionHelper.java index a16284cc0..d8dec7a1e 100644 --- a/utils/mongodb-reactive-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBReactiveCollectionHelper.java +++ b/utils/mongodb-reactive-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBReactiveCollectionHelper.java @@ -49,10 +49,18 @@ public Iterable listIndexes() { } @Override - public String createUniqueIndex(MongoDBDocumentHelper uniqueIndexDocument) { - return first(collection.createIndex( - uniqueIndexDocument.getDocument(), - new IndexOptions().unique(true))); + public String createIndex(MongoDBDocumentHelper keyDocument, + String name, + boolean unique, + MongoDBDocumentHelper partialFilterExpression) { + IndexOptions options = new IndexOptions().unique(unique); + if (name != null) { + options.name(name); + } + if (partialFilterExpression != null) { + options.partialFilterExpression(partialFilterExpression.getDocument()); + } + return first(collection.createIndex(keyDocument.getDocument(), options)); } @Override diff --git a/utils/mongodb-sync-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBSyncCollectionHelper.java b/utils/mongodb-sync-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBSyncCollectionHelper.java index 01993b567..4d0f9003c 100644 --- a/utils/mongodb-sync-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBSyncCollectionHelper.java +++ b/utils/mongodb-sync-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBSyncCollectionHelper.java @@ -40,8 +40,18 @@ public Iterable listIndexes() { } @Override - public String createUniqueIndex(MongoDBDocumentHelper uniqueIndexDocument) { - return collection.createIndex(uniqueIndexDocument.getDocument(), new IndexOptions().unique(true)); + public String createIndex(MongoDBDocumentHelper keyDocument, + String name, + boolean unique, + MongoDBDocumentHelper partialFilterExpression) { + IndexOptions options = new IndexOptions().unique(unique); + if (name != null) { + options.name(name); + } + if (partialFilterExpression != null) { + options.partialFilterExpression(partialFilterExpression.getDocument()); + } + return collection.createIndex(keyDocument.getDocument(), options); } @Override diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionHelper.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionHelper.java index fd6dad35d..b44685214 100644 --- a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionHelper.java +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionHelper.java @@ -21,7 +21,24 @@ public interface CollectionHelper { Iterable listIndexes(); - String createUniqueIndex(DOCUMENT uniqueIndexDocument); + /** + * Creates an index on the collection. + * + * @param keyDocument the index key specification (field → direction) + * @param name explicit index name, or {@code null} to let the server auto-generate it + * @param unique whether the index enforces uniqueness + * @param partialFilterExpression a partial-index filter, or {@code null} for a full index + * @return the resulting index name + */ + String createIndex(DOCUMENT keyDocument, String name, boolean unique, DOCUMENT partialFilterExpression); + + /** + * Backward-compatible shortcut for a plain unique index. Retained so existing callers keep working; + * delegates to {@link #createIndex(DocumentHelper, String, boolean, DocumentHelper)}. + */ + default String createUniqueIndex(DOCUMENT uniqueIndexDocument) { + return createIndex(uniqueIndexDocument, null, true, null); + } void dropIndex(String name); diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionInitializator.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionInitializator.java index 908b303bd..a95341918 100644 --- a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionInitializator.java +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/CollectionInitializator.java @@ -19,10 +19,13 @@ import io.flamingock.internal.util.log.FlamingockLoggerFactory; import org.slf4j.Logger; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.stream.StreamSupport; public class CollectionInitializator { @@ -32,18 +35,30 @@ public class CollectionInitializator { private final static int INDEX_ENSURE_MAX_TRIES = 3; - private final String[] uniqueFields; + private final List indexDefinitions; private final Supplier documentWrapperSupplier; private boolean ensuredCollectionIndex = false; private final CollectionHelper collectionWrapper; + /** + * Backward-compatible constructor: a single ascending unique index on {@code uniqueFields}. + * Retained so existing callers (audit, lock, target-system markers) keep behaving identically. + */ public CollectionInitializator(CollectionHelper collectionWrapper, Supplier documentWrapperSupplier, String[] uniqueFields) { + this(collectionWrapper, + documentWrapperSupplier, + Collections.singletonList(IndexDefinition.uniqueOn(uniqueFields))); + } + + public CollectionInitializator(CollectionHelper collectionWrapper, + Supplier documentWrapperSupplier, + List indexDefinitions) { this.collectionWrapper = collectionWrapper; this.documentWrapperSupplier = documentWrapperSupplier; - this.uniqueFields = uniqueFields; + this.indexDefinitions = new ArrayList<>(indexDefinitions); } @@ -65,22 +80,20 @@ private void ensureIndex(int tryCounter) { throw new RuntimeException("Max tries " + INDEX_ENSURE_MAX_TRIES + " index creation"); } if (isIndexWrong()) { - cleanResidualUniqueKeys(); - if (indexCreatedNotRequired()) { - createRequiredUniqueIndex(); - } + cleanResidualKeys(); + createMissingIndexes(); ensureIndex(tryCounter - 1); } } protected boolean isIndexWrong() { - return !getResidualKeys().isEmpty() || indexCreatedNotRequired(); + return !getResidualKeys().isEmpty() || !getMissingSpecs().isEmpty(); } - protected void cleanResidualUniqueKeys() { - logger.debug("Removing residual uniqueKeys for collection [{}]", getCollectionName()); + protected void cleanResidualKeys() { + logger.debug("Removing residual indexes for collection [{}]", getCollectionName()); getResidualKeys().stream() - .peek(index -> logger.debug("Removed residual uniqueKey [{}] for collection [{}]", index.toString(), getCollectionName())) + .peek(index -> logger.debug("Removed residual index [{}] for collection [{}]", index.toString(), getCollectionName())) .forEach(this::dropIndex); } @@ -95,30 +108,83 @@ private Iterable listIndexes() { } protected boolean doesNeedToBeRemoved(DocumentHelper index) { - return !isIdIndex(index) && isUniqueIndex(index) && !isRightIndex(index); + if (isIdIndex(index)) { + return false; + } + boolean matchesAnySpec = indexDefinitions.stream().anyMatch(spec -> matchesSpec(index, spec)); + // Rule (a) — legacy behavior, unchanged for the single-unique callers: a unique, non-_id index + // that matches none of the desired specs is residual and must be dropped/recreated. + if (isUniqueIndex(index) && !matchesAnySpec) { + return true; + } + // Rule (b) — only fires for specs carrying an explicit name (i.e. never for the audit/lock specs, + // whose name is null): an existing index reusing a requested name but not matching that spec is a + // stale/mis-defined index that must be dropped so the correct one can be recreated. + String indexName = index.get("name") != null ? index.get("name").toString() : null; + if (indexName != null) { + for (IndexDefinition spec : indexDefinitions) { + if (spec.getName() != null && spec.getName().equals(indexName) && !matchesSpec(index, spec)) { + return true; + } + } + } + return false; } protected boolean isIdIndex(DocumentHelper index) { return index.getWithWrapper("key").get("_id") != null; } - protected boolean indexCreatedNotRequired() { - return StreamSupport.stream( - collectionWrapper.listIndexes().spliterator(), - false) - .noneMatch(this::isRightIndex); + protected List getMissingSpecs() { + List existing = StreamSupport.stream(listIndexes().spliterator(), false) + .collect(Collectors.toList()); + return indexDefinitions.stream() + .filter(spec -> existing.stream().noneMatch(index -> matchesSpec(index, spec))) + .collect(Collectors.toList()); } - protected void createRequiredUniqueIndex() { - collectionWrapper.createUniqueIndex(getIndexDocument(uniqueFields)); - logger.debug("Index in collection [{}] was recreated", getCollectionName()); + protected void createMissingIndexes() { + for (IndexDefinition spec : getMissingSpecs()) { + collectionWrapper.createIndex( + buildKeyDocument(spec), + spec.getName(), + spec.isUnique(), + buildPartialFilterDocument(spec)); + logger.debug("Index {} in collection [{}] was created", spec.getName(), getCollectionName()); + } } - protected boolean isRightIndex(DocumentHelper index) { + protected boolean matchesSpec(DocumentHelper index, IndexDefinition spec) { final DocumentHelper key = index.getWithWrapper("key"); - boolean keyContainsAllFields = Stream.of(uniqueFields).allMatch(uniqueField -> key.get(uniqueField) != null); - boolean onlyTheseFields = key.size() == uniqueFields.length; - return keyContainsAllFields && onlyTheseFields && isUniqueIndex(index); + boolean keyContainsAllFields = spec.getKeys().keySet().stream().allMatch(field -> key.get(field) != null); + boolean onlyTheseFields = key.size() == spec.getKeys().size(); + if (!keyContainsAllFields || !onlyTheseFields) { + return false; + } + if (isUniqueIndex(index) != spec.isUnique()) { + return false; + } + return partialFilterMatches(index, spec); + } + + private boolean partialFilterMatches(DocumentHelper index, IndexDefinition spec) { + boolean indexHasPartial = index.containsKey("partialFilterExpression"); + if (indexHasPartial != spec.hasPartialFilter()) { + return false; + } + if (!spec.hasPartialFilter()) { + return true; + } + DocumentHelper stored = index.getWithWrapper("partialFilterExpression"); + if (stored.size() != spec.getPartialFilterExpression().size()) { + return false; + } + for (Map.Entry entry : spec.getPartialFilterExpression().entrySet()) { + if (!Objects.equals(stored.get(entry.getKey()), entry.getValue())) { + return false; + } + } + return true; } protected boolean isUniqueIndex(DocumentHelper index) { @@ -129,10 +195,19 @@ private String getCollectionName() { return collectionWrapper.getCollectionName(); } - protected DOCUMENT_WRAPPER getIndexDocument(String[] uniqueFields) { - final DOCUMENT_WRAPPER indexDocument = documentWrapperSupplier.get(); - Stream.of(uniqueFields).forEach(field -> indexDocument.append(field, 1)); - return indexDocument; + protected DOCUMENT_WRAPPER buildKeyDocument(IndexDefinition spec) { + final DOCUMENT_WRAPPER keyDocument = documentWrapperSupplier.get(); + spec.getKeys().forEach(keyDocument::append); + return keyDocument; + } + + protected DOCUMENT_WRAPPER buildPartialFilterDocument(IndexDefinition spec) { + if (!spec.hasPartialFilter()) { + return null; + } + final DOCUMENT_WRAPPER partialDocument = documentWrapperSupplier.get(); + spec.getPartialFilterExpression().forEach(partialDocument::append); + return partialDocument; } protected void dropIndex(DocumentHelper index) { diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/IndexDefinition.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/IndexDefinition.java new file mode 100644 index 000000000..a1799258d --- /dev/null +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/IndexDefinition.java @@ -0,0 +1,84 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.mongodb; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Persistence-agnostic description of a desired collection index. + *

+ * Supports the three variants the audit stores need: a plain unique index (the legacy shape used + * by the audit and lock collections), a non-unique index, and a partial index (an index that only + * covers documents matching a {@code partialFilterExpression}). The concrete {@link CollectionHelper} + * translates this definition into a driver-specific index at creation time. + */ +public final class IndexDefinition { + + private final LinkedHashMap keys; + private final boolean unique; + private final Map partialFilterExpression; + private final String name; + + public IndexDefinition(LinkedHashMap keys, + boolean unique, + Map partialFilterExpression, + String name) { + if (keys == null || keys.isEmpty()) { + throw new IllegalArgumentException("index keys must not be empty"); + } + this.keys = new LinkedHashMap<>(keys); + this.unique = unique; + this.partialFilterExpression = partialFilterExpression == null + ? null + : Collections.unmodifiableMap(new LinkedHashMap<>(partialFilterExpression)); + this.name = name; + } + + /** + * Builds a plain ascending unique index on the given fields. This reproduces the exact shape + * historically created for the audit and lock collections (all fields ascending, {@code unique=true}, + * no partial filter, server-generated name). + */ + public static IndexDefinition uniqueOn(String... fields) { + LinkedHashMap keys = new LinkedHashMap<>(); + for (String field : fields) { + keys.put(field, 1); + } + return new IndexDefinition(keys, true, null, null); + } + + public LinkedHashMap getKeys() { + return keys; + } + + public boolean isUnique() { + return unique; + } + + public Map getPartialFilterExpression() { + return partialFilterExpression; + } + + public boolean hasPartialFilter() { + return partialFilterExpression != null && !partialFilterExpression.isEmpty(); + } + + public String getName() { + return name; + } +} diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java new file mode 100644 index 000000000..1f1ff1979 --- /dev/null +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.mongodb; + +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.journal.JournalEvent; +import io.flamingock.internal.common.core.journal.JournalEventType; +import org.bson.Document; + +import java.time.Instant; +import java.util.Date; + +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_ACKNOWLEDGED; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_DATA; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_ID; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_TYPE; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_VERSION; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_OCCURRED_AT; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_ID; +import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_SEQUENCE; + +/** + * Maps a {@link JournalEvent} carrying an {@link AuditEntry} payload to/from a MongoDB {@link Document}. + *

+ * The {@code data} payload is nested as a sub-document, delegating to {@link MongoDBAuditMapper} so the + * audit representation stays single-sourced. {@code occurredAt} is stored as a BSON {@link Date} + * ({@code TimeUtil} has no {@link Instant} support), which is sufficient for the event buffer. + *

+ * Only {@link JournalEventType#CHANGE_STATE} events carry an {@link AuditEntry} payload today. Other + * event types (e.g. {@link JournalEventType#EXECUTION_STATE}) carry different payloads and are not yet + * implemented, so this mapper rejects them rather than silently mis-mapping their data as an audit entry. + */ +public class MongoDBEventMapper { + + /** The only event type whose {@code data} is an {@link AuditEntry} and is supported for now. */ + private static final JournalEventType SUPPORTED_EVENT_TYPE = JournalEventType.CHANGE_STATE; + + private final MongoDBAuditMapper dataMapper = + new MongoDBAuditMapper<>(() -> new MongoDBDocumentHelper(new Document())); + + public Document toDocument(JournalEvent event) { + requireSupportedType(event.getEventType()); + Document document = new Document(); + document.append(KEY_EVENT_ID, event.getEventId()); + document.append(KEY_EVENT_TYPE, event.getEventType().name()); + document.append(KEY_EVENT_VERSION, event.getEventVersion()); + document.append(KEY_STREAM_ID, event.getStreamId()); + document.append(KEY_STREAM_SEQUENCE, event.getStreamSequence()); + document.append(KEY_OCCURRED_AT, Date.from(event.getOccurredAt())); + document.append(KEY_ACKNOWLEDGED, event.isAcknowledged()); + document.append(KEY_DATA, dataMapper.toDocument(event.getData()).getDocument()); + return document; + } + + public JournalEvent fromDocument(Document document) { + JournalEventType eventType = JournalEventType.valueOf(document.getString(KEY_EVENT_TYPE)); + requireSupportedType(eventType); + AuditEntry data = dataMapper.fromDocument(new MongoDBDocumentHelper(document.get(KEY_DATA, Document.class))); + Instant occurredAt = ((Date) document.get(KEY_OCCURRED_AT)).toInstant(); + return new JournalEvent<>( + document.getString(KEY_EVENT_ID), + eventType, + ((Number) document.get(KEY_EVENT_VERSION)).intValue(), + document.getString(KEY_STREAM_ID), + ((Number) document.get(KEY_STREAM_SEQUENCE)).longValue(), + occurredAt, + data, + document.getBoolean(KEY_ACKNOWLEDGED, false)); + } + + private static void requireSupportedType(JournalEventType eventType) { + if (eventType != SUPPORTED_EVENT_TYPE) { + throw new UnsupportedOperationException( + "MongoDBEventMapper only supports " + SUPPORTED_EVENT_TYPE + " events (AuditEntry payload); " + + "event type " + eventType + " is not yet implemented"); + } + } +} diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java new file mode 100644 index 000000000..77e7dbbff --- /dev/null +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.mongodb.event; + +/** + * BSON field names for the local event-buffer collection ({@code flamingockEvents}). + *

+ * Declared locally because the shared {@code AuditEntryFieldConstants}/{@code CommunityPersistenceConstants} + * live in the external {@code flamingock-general-util} artifact and cannot be extended from this repository. + */ +public final class EventFieldConstants { + + public static final String KEY_EVENT_ID = "eventId"; + public static final String KEY_EVENT_TYPE = "eventType"; + public static final String KEY_EVENT_VERSION = "eventVersion"; + public static final String KEY_STREAM_ID = "streamId"; + public static final String KEY_STREAM_SEQUENCE = "streamSequence"; + public static final String KEY_OCCURRED_AT = "occurredAt"; + public static final String KEY_DATA = "data"; + public static final String KEY_ACKNOWLEDGED = "acknowledged"; + + private EventFieldConstants() { + } +} diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java new file mode 100644 index 000000000..14e1c7c05 --- /dev/null +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.mongodb.event; + +/** + * Default persistence name for the local event buffer, mirroring the (external, un-extendable) + * {@code CommunityPersistenceConstants} defaults used for the audit and lock collections. + */ +public final class EventPersistenceConstants { + + public static final String DEFAULT_EVENTS_STORE_NAME = "flamingockEvents"; + + private EventPersistenceConstants() { + } +} diff --git a/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java b/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java new file mode 100644 index 000000000..d1dee41e2 --- /dev/null +++ b/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.internal.common.mongodb; + +import io.flamingock.core.kit.audit.AuditEntryTestFactory; +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.audit.AuditTxType; +import io.flamingock.internal.common.core.journal.JournalEvent; +import io.flamingock.internal.common.core.journal.JournalEventType; +import org.bson.Document; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MongoDBEventMapperTest { + + private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + + @Test + void roundTripsChangeStateEventWithAuditEntryPayload() { + Instant occurredAt = Instant.parse("2026-07-21T10:15:30Z"); + JournalEvent event = new JournalEvent<>( + "evt-1", + JournalEventType.CHANGE_STATE, + JournalEvent.DEFAULT_VERSION, + "stageA", + 7L, + occurredAt, + auditEntry("change-1"), + true); + + JournalEvent restored = mapper.fromDocument(mapper.toDocument(event)); + + assertEquals("evt-1", restored.getEventId()); + assertEquals(JournalEventType.CHANGE_STATE, restored.getEventType()); + assertEquals(JournalEvent.DEFAULT_VERSION, restored.getEventVersion()); + assertEquals("stageA", restored.getStreamId()); + assertEquals(7L, restored.getStreamSequence()); + assertEquals(occurredAt, restored.getOccurredAt()); + assertTrue(restored.isAcknowledged()); + assertEquals("change-1", restored.getData().getChangeId()); + assertEquals(AuditEntry.Status.APPLIED, restored.getData().getState()); + } + + @Test + void storesEventFieldsUnderTheAgreedBsonNames() { + JournalEvent event = new JournalEvent<>( + "evt-4", + JournalEventType.CHANGE_STATE, + "stageA", + 3L, + Instant.parse("2026-07-21T10:15:30Z"), + auditEntry("change-4")); + + Document document = mapper.toDocument(event); + + assertEquals("evt-4", document.getString("eventId")); + assertEquals("stageA", document.getString("streamId")); + assertEquals(3L, document.get("streamSequence")); + // acknowledged must be persisted as a real boolean false: the partial index filters on it. + assertEquals(Boolean.FALSE, document.get("acknowledged")); + assertTrue(document.get("data") instanceof Document); + } + + @Test + void toDocumentRejectsUnsupportedEventType() { + JournalEvent executionEvent = new JournalEvent<>( + "evt-2", + JournalEventType.EXECUTION_STATE, + JournalEvent.DEFAULT_VERSION, + "stageA", + 1L, + Instant.parse("2026-07-21T10:15:30Z"), + auditEntry("change-2"), + false); + + assertThrows(UnsupportedOperationException.class, () -> mapper.toDocument(executionEvent)); + } + + @Test + void fromDocumentRejectsUnsupportedEventType() { + Document stored = new Document("eventType", JournalEventType.EXECUTION_STATE.name()) + .append("eventId", "evt-3"); + + assertThrows(UnsupportedOperationException.class, () -> mapper.fromDocument(stored)); + } + + private static AuditEntry auditEntry(String changeId) { + return AuditEntryTestFactory.createTestAuditEntry( + changeId, AuditEntry.Status.APPLIED, AuditTxType.NON_TX, (Class) null); + } +} From 22f39b9d4cb5e6039300b77271d3993192931dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 15:03:51 +0200 Subject: [PATCH 2/7] refactor: jorunal renaming --- ...ngoDBSyncEventStore.java => MongoDBSyncJournalEventStore.java} | 0 ...StoreE2ETest.java => MongoDBSyncJournalEventStoreE2ETest.java} | 0 .../{event/EventStore.java => journal/JournalEventStore.java} | 0 .../{MongoDBEventMapper.java => MongoDBJournalEventMapper.java} | 0 .../JournalEventFieldConstants.java} | 0 .../JournalEventPersistenceConstants.java} | 0 ...oDBEventMapperTest.java => MongoDBJournalEventMapperTest.java} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/{MongoDBSyncEventStore.java => MongoDBSyncJournalEventStore.java} (100%) rename community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/{MongoDBSyncEventStoreE2ETest.java => MongoDBSyncJournalEventStoreE2ETest.java} (100%) rename core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/{event/EventStore.java => journal/JournalEventStore.java} (100%) rename utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/{MongoDBEventMapper.java => MongoDBJournalEventMapper.java} (100%) rename utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/{event/EventFieldConstants.java => journal/JournalEventFieldConstants.java} (100%) rename utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/{event/EventPersistenceConstants.java => journal/JournalEventPersistenceConstants.java} (100%) rename utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/{MongoDBEventMapperTest.java => MongoDBJournalEventMapperTest.java} (100%) diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java similarity index 100% rename from community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStore.java rename to community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java diff --git a/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java b/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java similarity index 100% rename from community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncEventStoreE2ETest.java rename to community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java similarity index 100% rename from core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/event/EventStore.java rename to core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java similarity index 100% rename from utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBEventMapper.java rename to utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java similarity index 100% rename from utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventFieldConstants.java rename to utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java similarity index 100% rename from utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/event/EventPersistenceConstants.java rename to utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java diff --git a/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java b/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java similarity index 100% rename from utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBEventMapperTest.java rename to utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java From 2b99adfc86e5855176c8f788305048bb9b8390fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 15:16:04 +0200 Subject: [PATCH 3/7] refactor: jorunal renaming --- .../mongodb/sync/MongoDBSyncAuditStore.java | 22 +++++----- .../internal/MongoDBSyncAuditPersistence.java | 15 +++---- .../MongoDBSyncJournalEventStore.java | 20 ++++----- .../MongoDBSyncJournalEventStoreE2ETest.java | 44 +++++++++---------- .../community/CommunityAuditPersistence.java | 1 - .../store/journal/JournalEventStore.java | 4 +- .../mongodb/MongoDBJournalEventMapper.java | 20 ++++----- .../journal/JournalEventFieldConstants.java | 8 ++-- .../JournalEventPersistenceConstants.java | 8 ++-- .../MongoDBJournalEventMapperTest.java | 4 +- 10 files changed, 72 insertions(+), 74 deletions(-) diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java index 1fdc67eec..0db68ffc3 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/MongoDBSyncAuditStore.java @@ -32,7 +32,7 @@ import io.flamingock.store.mongodb.sync.internal.MongoDBSyncLockService; import io.flamingock.externalsystem.mongodb.api.MongoDBExternalSystem; -import static io.flamingock.internal.common.mongodb.event.EventPersistenceConstants.DEFAULT_EVENTS_STORE_NAME; +import static io.flamingock.internal.common.mongodb.journal.JournalEventPersistenceConstants.DEFAULT_JOURNAL_STORE_NAME; import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME; import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_LOCK_STORE_NAME; @@ -47,7 +47,7 @@ public class MongoDBSyncAuditStore implements CommunityAuditStore { private MongoDatabase database; private String auditRepositoryName = DEFAULT_AUDIT_STORE_NAME; private String lockRepositoryName = DEFAULT_LOCK_STORE_NAME; - private String eventsRepositoryName = DEFAULT_EVENTS_STORE_NAME; + private String journalRepositoryName = DEFAULT_JOURNAL_STORE_NAME; private ReadConcern readConcern = ReadConcern.MAJORITY; private ReadPreference readPreference = ReadPreference.primary(); private WriteConcern writeConcern = WriteConcern.MAJORITY.withJournal(true); @@ -87,8 +87,8 @@ public MongoDBSyncAuditStore withLockRepositoryName(String lockRepositoryName) { return this; } - public MongoDBSyncAuditStore withEventsRepositoryName(String eventsRepositoryName) { - this.eventsRepositoryName = eventsRepositoryName; + public MongoDBSyncAuditStore withJournalRepositoryName(String journalRepositoryName) { + this.journalRepositoryName = journalRepositoryName; return this; } @@ -127,7 +127,7 @@ public synchronized CommunityAuditPersistence getPersistence() { communityConfiguration, database, auditRepositoryName, - eventsRepositoryName, + journalRepositoryName, readConcern, readPreference, writeConcern, @@ -169,20 +169,20 @@ private void validate() { throw new FlamingockException("The 'lockRepositoryName' property is required."); } - if (eventsRepositoryName == null || eventsRepositoryName.trim().isEmpty()) { - throw new FlamingockException("The 'eventsRepositoryName' property is required."); + if (journalRepositoryName == null || journalRepositoryName.trim().isEmpty()) { + throw new FlamingockException("The 'journalRepositoryName' property is required."); } if (auditRepositoryName.trim().equalsIgnoreCase(lockRepositoryName.trim())) { throw new FlamingockException("The 'auditRepositoryName' and 'lockRepositoryName' properties must not be the same."); } - if (eventsRepositoryName.trim().equalsIgnoreCase(auditRepositoryName.trim())) { - throw new FlamingockException("The 'eventsRepositoryName' and 'auditRepositoryName' properties must not be the same."); + if (journalRepositoryName.trim().equalsIgnoreCase(auditRepositoryName.trim())) { + throw new FlamingockException("The 'journalRepositoryName' and 'auditRepositoryName' properties must not be the same."); } - if (eventsRepositoryName.trim().equalsIgnoreCase(lockRepositoryName.trim())) { - throw new FlamingockException("The 'eventsRepositoryName' and 'lockRepositoryName' properties must not be the same."); + if (journalRepositoryName.trim().equalsIgnoreCase(lockRepositoryName.trim())) { + throw new FlamingockException("The 'journalRepositoryName' and 'lockRepositoryName' properties must not be the same."); } if (readConcern == null) { diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java index d3bd1d0d8..b5b554562 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditPersistence.java @@ -23,7 +23,6 @@ import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.core.configuration.community.CommunityConfigurable; import io.flamingock.internal.core.external.store.audit.community.AbstractCommunityAuditPersistence; -import io.flamingock.internal.core.external.store.event.EventStore; import io.flamingock.internal.util.Result; import io.flamingock.internal.util.id.RunnerId; @@ -35,10 +34,10 @@ public class MongoDBSyncAuditPersistence extends AbstractCommunityAuditPersistence { private MongoDBSyncAuditor auditor; - private MongoDBSyncEventStore eventStore; + private MongoDBSyncJournalEventStore journalEventStore; private final MongoDatabase database; private final String auditCollectionName; - private final String eventsCollectionName; + private final String journalCollectionName; private final ReadConcern readConcern; private final ReadPreference readPreference; private final WriteConcern writeConcern; @@ -48,7 +47,7 @@ public class MongoDBSyncAuditPersistence extends AbstractCommunityAuditPersisten public MongoDBSyncAuditPersistence(CommunityConfigurable localConfiguration, MongoDatabase database, String auditCollectionName, - String eventsCollectionName, + String journalCollectionName, ReadConcern readConcern, ReadPreference readPreference, WriteConcern writeConcern, @@ -56,7 +55,7 @@ public MongoDBSyncAuditPersistence(CommunityConfigurable localConfiguration, super(localConfiguration); this.database = database; this.auditCollectionName = auditCollectionName; - this.eventsCollectionName = eventsCollectionName; + this.journalCollectionName = journalCollectionName; this.readConcern = readConcern; this.readPreference = readPreference; this.writeConcern = writeConcern; @@ -68,9 +67,9 @@ protected void doInitialize(RunnerId runnerId) { //Auditor auditor = new MongoDBSyncAuditor(database, auditCollectionName, readConcern, readPreference, writeConcern); auditor.initialize(autoCreate); - //Event buffer - eventStore = new MongoDBSyncEventStore(database, eventsCollectionName, readConcern, readPreference, writeConcern); - eventStore.initialize(autoCreate); + //Journal + journalEventStore = new MongoDBSyncJournalEventStore(database, journalCollectionName, readConcern, readPreference, writeConcern); + journalEventStore.initialize(autoCreate); } @Deprecated diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java index 187c1f97b..f4ac53358 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStore.java @@ -28,9 +28,9 @@ import io.flamingock.internal.common.mongodb.CollectionInitializator; import io.flamingock.internal.common.mongodb.IndexDefinition; import io.flamingock.internal.common.mongodb.MongoDBDocumentHelper; -import io.flamingock.internal.common.mongodb.MongoDBEventMapper; +import io.flamingock.internal.common.mongodb.MongoDBJournalEventMapper; import io.flamingock.internal.common.mongodb.MongoDBSyncCollectionHelper; -import io.flamingock.internal.core.external.store.event.EventStore; +import io.flamingock.internal.core.external.store.journal.JournalEventStore; import org.bson.Document; import java.util.ArrayList; @@ -41,26 +41,26 @@ import java.util.Map; import java.util.Optional; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_ACKNOWLEDGED; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_ID; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_ID; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_SEQUENCE; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_ACKNOWLEDGED; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_EVENT_ID; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_STREAM_ID; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_STREAM_SEQUENCE; /** - * MongoDB-sync implementation of the local event buffer ({@code flamingockEvents}). + * MongoDB-sync implementation of the local journal ({@code flamingockJournalEvents}). *

* Sibling of {@link MongoDBSyncAuditor}/{@link MongoDBSyncLockService}: it owns its own collection and * index setup. Phase 1 is read/acknowledge only — event writing (atomic with the state write) is a later phase. */ -public class MongoDBSyncEventStore implements EventStore { +public class MongoDBSyncJournalEventStore implements JournalEventStore { static final String UNIQUE_INDEX_NAME = "unique_key_sequence"; static final String UNACKNOWLEDGED_INDEX_NAME = "unacknowledged_by_key_sequence"; private final MongoCollection collection; - private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + private final MongoDBJournalEventMapper mapper = new MongoDBJournalEventMapper(); - MongoDBSyncEventStore(MongoDatabase database, + MongoDBSyncJournalEventStore(MongoDatabase database, String collectionName, ReadConcern readConcern, ReadPreference readPreference, diff --git a/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java b/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java index b94810d22..1e8e1003d 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java +++ b/community/flamingock-mongodb-sync-auditstore/src/test/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncJournalEventStoreE2ETest.java @@ -27,7 +27,7 @@ import io.flamingock.internal.common.core.audit.AuditTxType; import io.flamingock.internal.common.core.journal.JournalEvent; import io.flamingock.internal.common.core.journal.JournalEventType; -import io.flamingock.internal.common.mongodb.MongoDBEventMapper; +import io.flamingock.internal.common.mongodb.MongoDBJournalEventMapper; import org.bson.Document; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -54,29 +54,29 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @Testcontainers -class MongoDBSyncEventStoreE2ETest { +class MongoDBSyncJournalEventStoreE2ETest { private static final String DB_NAME = "test"; - private static final String EVENTS_COLLECTION = "flamingockEvents"; + private static final String JOURNAL_COLLECTION = "flamingockJournalEvents"; @Container public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")).withReuse(true); - private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + private final MongoDBJournalEventMapper mapper = new MongoDBJournalEventMapper(); private MongoClient mongoClient; private MongoDatabase database; - private MongoDBSyncEventStore eventStore; + private MongoDBSyncJournalEventStore journalEventStore; @BeforeEach void setUp() { mongoClient = MongoClients.create(mongoDBContainer.getConnectionString()); database = mongoClient.getDatabase(DB_NAME); - eventStore = new MongoDBSyncEventStore( - database, EVENTS_COLLECTION, + journalEventStore = new MongoDBSyncJournalEventStore( + database, JOURNAL_COLLECTION, ReadConcern.MAJORITY, ReadPreference.primary(), WriteConcern.MAJORITY.withJournal(true)); - eventStore.initialize(true); + journalEventStore.initialize(true); } @AfterEach @@ -90,12 +90,12 @@ void tearDown() { void createsExpectedIndexes() { Map byName = listIndexesByName(); - Document unique = byName.get(MongoDBSyncEventStore.UNIQUE_INDEX_NAME); + Document unique = byName.get(MongoDBSyncJournalEventStore.UNIQUE_INDEX_NAME); assertNotNull(unique, "unique index missing"); assertTrue(unique.getBoolean("unique", false), "index should be unique"); assertEquals(new Document("streamId", 1).append("streamSequence", 1), unique.get("key")); - Document unacked = byName.get(MongoDBSyncEventStore.UNACKNOWLEDGED_INDEX_NAME); + Document unacked = byName.get(MongoDBSyncJournalEventStore.UNACKNOWLEDGED_INDEX_NAME); assertNotNull(unacked, "unacknowledged partial index missing"); assertFalse(unacked.getBoolean("unique", false), "partial index should not be unique"); assertEquals(new Document("acknowledged", 1).append("streamId", 1).append("streamSequence", 1), unacked.get("key")); @@ -107,9 +107,9 @@ void createsExpectedIndexes() { void initializeIsIdempotent() { Map before = listIndexesByName(); - eventStore.initialize(true); - MongoDBSyncEventStore secondInstance = new MongoDBSyncEventStore( - database, EVENTS_COLLECTION, + journalEventStore.initialize(true); + MongoDBSyncJournalEventStore secondInstance = new MongoDBSyncJournalEventStore( + database, JOURNAL_COLLECTION, ReadConcern.MAJORITY, ReadPreference.primary(), WriteConcern.MAJORITY.withJournal(true)); secondInstance.initialize(true); @@ -134,12 +134,12 @@ void getLastEventByStreamReturnsHighestSequence() { event("evt-A3", "stageA", 3L, false), event("evt-B1", "stageB", 1L, false))); - Optional> last = eventStore.getLastEventByStream("stageA"); + Optional> last = journalEventStore.getLastEventByStream("stageA"); assertTrue(last.isPresent()); assertEquals("evt-A3", last.get().getEventId()); assertEquals(3L, last.get().getStreamSequence()); - assertFalse(eventStore.getLastEventByStream("unknown-stream").isPresent()); + assertFalse(journalEventStore.getLastEventByStream("unknown-stream").isPresent()); } @Test @@ -151,10 +151,10 @@ void getUnacknowledgedEventsOrderedAndLimited() { event("evt-A3", "stageA", 3L, false), event("evt-B1", "stageB", 1L, false))); - List all = ids(eventStore.getUnacknowledgedEvents(10)); + List all = ids(journalEventStore.getUnacknowledgedEvents(10)); assertEquals(Arrays.asList("evt-A2", "evt-A3", "evt-B1"), all); - List firstTwo = ids(eventStore.getUnacknowledgedEvents(2)); + List firstTwo = ids(journalEventStore.getUnacknowledgedEvents(2)); assertEquals(Arrays.asList("evt-A2", "evt-A3"), firstTwo); } @@ -166,18 +166,18 @@ void acknowledgeEventsRemovesFromBatch() { event("evt-A3", "stageA", 3L, false), event("evt-B1", "stageB", 1L, false))); - long modified = eventStore.acknowledgeEvents(Arrays.asList("evt-A2", "evt-B1")); + long modified = journalEventStore.acknowledgeEvents(Arrays.asList("evt-A2", "evt-B1")); assertEquals(2L, modified); - assertEquals(Collections.singletonList("evt-A3"), ids(eventStore.getUnacknowledgedEvents(10))); + assertEquals(Collections.singletonList("evt-A3"), ids(journalEventStore.getUnacknowledgedEvents(10))); - assertEquals(0L, eventStore.acknowledgeEvents(Collections.emptyList())); + assertEquals(0L, journalEventStore.acknowledgeEvents(Collections.emptyList())); } // ----------------------------- helpers ----------------------------- private Map listIndexesByName() { - return database.getCollection(EVENTS_COLLECTION).listIndexes() + return database.getCollection(JOURNAL_COLLECTION).listIndexes() .into(new ArrayList<>()) .stream() .filter(index -> index.getString("name") != null) @@ -185,7 +185,7 @@ private Map listIndexesByName() { } private void seed(List> events) { - MongoCollection collection = database.getCollection(EVENTS_COLLECTION); + MongoCollection collection = database.getCollection(JOURNAL_COLLECTION); List documents = events.stream().map(mapper::toDocument).collect(Collectors.toList()); collection.insertMany(documents); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java index 6508187d3..e89d88f16 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/community/CommunityAuditPersistence.java @@ -16,7 +16,6 @@ package io.flamingock.internal.core.external.store.audit.community; import io.flamingock.internal.core.external.store.audit.AuditPersistence; -import io.flamingock.internal.core.external.store.event.EventStore; public interface CommunityAuditPersistence extends AuditPersistence, CommunityAuditReader { } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java index c2c92e342..559266ad4 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/journal/JournalEventStore.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.flamingock.internal.core.external.store.event; +package io.flamingock.internal.core.external.store.journal; import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.journal.JournalEvent; @@ -31,7 +31,7 @@ *

* Phase 1 exposes read/acknowledge only. Event writing (atomic with the state write) is a later phase. */ -public interface EventStore { +public interface JournalEventStore { /** * Returns the event with the highest {@code streamSequence} for the given stream, if any. diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java index 1f1ff1979..9513c3207 100644 --- a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapper.java @@ -23,14 +23,14 @@ import java.time.Instant; import java.util.Date; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_ACKNOWLEDGED; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_DATA; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_ID; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_TYPE; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_EVENT_VERSION; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_OCCURRED_AT; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_ID; -import static io.flamingock.internal.common.mongodb.event.EventFieldConstants.KEY_STREAM_SEQUENCE; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_ACKNOWLEDGED; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_DATA; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_EVENT_ID; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_EVENT_TYPE; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_EVENT_VERSION; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_OCCURRED_AT; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_STREAM_ID; +import static io.flamingock.internal.common.mongodb.journal.JournalEventFieldConstants.KEY_STREAM_SEQUENCE; /** * Maps a {@link JournalEvent} carrying an {@link AuditEntry} payload to/from a MongoDB {@link Document}. @@ -43,7 +43,7 @@ * event types (e.g. {@link JournalEventType#EXECUTION_STATE}) carry different payloads and are not yet * implemented, so this mapper rejects them rather than silently mis-mapping their data as an audit entry. */ -public class MongoDBEventMapper { +public class MongoDBJournalEventMapper { /** The only event type whose {@code data} is an {@link AuditEntry} and is supported for now. */ private static final JournalEventType SUPPORTED_EVENT_TYPE = JournalEventType.CHANGE_STATE; @@ -84,7 +84,7 @@ public JournalEvent fromDocument(Document document) { private static void requireSupportedType(JournalEventType eventType) { if (eventType != SUPPORTED_EVENT_TYPE) { throw new UnsupportedOperationException( - "MongoDBEventMapper only supports " + SUPPORTED_EVENT_TYPE + " events (AuditEntry payload); " + "MongoDBJournalEventMapper only supports " + SUPPORTED_EVENT_TYPE + " events (AuditEntry payload); " + "event type " + eventType + " is not yet implemented"); } } diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java index 77e7dbbff..e74c286ae 100644 --- a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventFieldConstants.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.flamingock.internal.common.mongodb.event; +package io.flamingock.internal.common.mongodb.journal; /** - * BSON field names for the local event-buffer collection ({@code flamingockEvents}). + * BSON field names for the local journal collection ({@code flamingockJournalEvents}). *

* Declared locally because the shared {@code AuditEntryFieldConstants}/{@code CommunityPersistenceConstants} * live in the external {@code flamingock-general-util} artifact and cannot be extended from this repository. */ -public final class EventFieldConstants { +public final class JournalEventFieldConstants { public static final String KEY_EVENT_ID = "eventId"; public static final String KEY_EVENT_TYPE = "eventType"; @@ -32,6 +32,6 @@ public final class EventFieldConstants { public static final String KEY_DATA = "data"; public static final String KEY_ACKNOWLEDGED = "acknowledged"; - private EventFieldConstants() { + private JournalEventFieldConstants() { } } diff --git a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java index 14e1c7c05..40c813278 100644 --- a/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java +++ b/utils/mongodb-util/src/main/java/io/flamingock/internal/common/mongodb/journal/JournalEventPersistenceConstants.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.flamingock.internal.common.mongodb.event; +package io.flamingock.internal.common.mongodb.journal; /** * Default persistence name for the local event buffer, mirroring the (external, un-extendable) * {@code CommunityPersistenceConstants} defaults used for the audit and lock collections. */ -public final class EventPersistenceConstants { +public final class JournalEventPersistenceConstants { - public static final String DEFAULT_EVENTS_STORE_NAME = "flamingockEvents"; + public static final String DEFAULT_JOURNAL_STORE_NAME = "flamingockJournalEvents"; - private EventPersistenceConstants() { + private JournalEventPersistenceConstants() { } } diff --git a/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java b/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java index d1dee41e2..671d99083 100644 --- a/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java +++ b/utils/mongodb-util/src/test/java/io/flamingock/internal/common/mongodb/MongoDBJournalEventMapperTest.java @@ -29,9 +29,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -class MongoDBEventMapperTest { +class MongoDBJournalEventMapperTest { - private final MongoDBEventMapper mapper = new MongoDBEventMapper(); + private final MongoDBJournalEventMapper mapper = new MongoDBJournalEventMapper(); @Test void roundTripsChangeStateEventWithAuditEntryPayload() { From 14c0631b0bd9441a520b9517e276b78e71a152fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 16:16:59 +0200 Subject: [PATCH 4/7] refactor: jorunal renaming --- .../transaction/TransactionalExternalSystem.java | 16 ++++++++++++++++ .../targets/TransactionalTargetSystem.java | 12 ++---------- .../build.gradle.kts | 3 ++- .../mongodb/api/MongoDBExternalSystem.java | 3 ++- 4 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java new file mode 100644 index 000000000..4440a457f --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java @@ -0,0 +1,16 @@ +package io.flamingock.internal.common.core.transaction; + +import io.flamingock.api.external.ExternalSystem; + +public interface TransactionalExternalSystem extends ExternalSystem { + + /** + * Returns the transaction wrapper for this target system. + *

+ * The wrapper is responsible for starting, committing, and rolling back transactions, + * as well as injecting transaction-scoped dependencies into the execution runtime. + * + * @return the transaction wrapper instance + */ + TransactionWrapper getTxWrapper(); +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TransactionalTargetSystem.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TransactionalTargetSystem.java index 28b06c050..fa338e6af 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TransactionalTargetSystem.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TransactionalTargetSystem.java @@ -19,6 +19,7 @@ import io.flamingock.internal.common.core.audit.AuditReaderType; import io.flamingock.internal.common.core.context.ContextInitializable; import io.flamingock.internal.common.core.context.RuntimeContext; +import io.flamingock.internal.common.core.transaction.TransactionalExternalSystem; import io.flamingock.internal.core.runtime.ExecutionRuntime; import io.flamingock.internal.core.external.targets.mark.NoOpTargetSystemAuditMarker; import io.flamingock.internal.core.external.targets.mark.TargetSystemAuditMarker; @@ -43,7 +44,7 @@ */ public abstract class TransactionalTargetSystem> extends AbstractTargetSystem - implements ContextInitializable { + implements TransactionalExternalSystem, ContextInitializable { protected boolean autoCreate = true; protected TargetSystemAuditMarker auditMarker; @@ -90,15 +91,6 @@ public TargetSystemAuditMarker getAuditMarker() { return auditMarker; } - /** - * Returns the transaction wrapper for this target system. - *

- * The wrapper is responsible for starting, committing, and rolling back transactions, - * as well as injecting transaction-scoped dependencies into the execution runtime. - * - * @return the transaction wrapper instance - */ - abstract public TransactionWrapper getTxWrapper(); /** * Returns an audit history reader for importing audit entries from external migration sources. diff --git a/core/target-systems/flamingock-mongodb-externalsystem-api/build.gradle.kts b/core/target-systems/flamingock-mongodb-externalsystem-api/build.gradle.kts index 9937d7fbe..55b8e566f 100644 --- a/core/target-systems/flamingock-mongodb-externalsystem-api/build.gradle.kts +++ b/core/target-systems/flamingock-mongodb-externalsystem-api/build.gradle.kts @@ -1,6 +1,7 @@ val coreApiVersion: String by extra dependencies { - implementation("io.flamingock:flamingock-core-api:${coreApiVersion}") + + api(project(":core:flamingock-core-commons")) // MongoDB driver for storage implementations compileOnly("org.mongodb:mongodb-driver-sync:4.0.0") diff --git a/core/target-systems/flamingock-mongodb-externalsystem-api/src/main/java/io/flamingock/externalsystem/mongodb/api/MongoDBExternalSystem.java b/core/target-systems/flamingock-mongodb-externalsystem-api/src/main/java/io/flamingock/externalsystem/mongodb/api/MongoDBExternalSystem.java index d5c5e4176..1f284c536 100644 --- a/core/target-systems/flamingock-mongodb-externalsystem-api/src/main/java/io/flamingock/externalsystem/mongodb/api/MongoDBExternalSystem.java +++ b/core/target-systems/flamingock-mongodb-externalsystem-api/src/main/java/io/flamingock/externalsystem/mongodb/api/MongoDBExternalSystem.java @@ -17,8 +17,9 @@ import com.mongodb.client.MongoDatabase; import io.flamingock.api.external.ExternalSystem; +import io.flamingock.internal.common.core.transaction.TransactionalExternalSystem; -public interface MongoDBExternalSystem extends ExternalSystem { +public interface MongoDBExternalSystem extends TransactionalExternalSystem { MongoDatabase getMongoDatabase(); } From 0c8ac9baf95c0e517eb092dbfa75f4fa342766c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 22:32:18 +0200 Subject: [PATCH 5/7] refactor: remove LifecycleAuditWriter --- .../cloud/CloudAuditPersistenceImpl.java | 6 +-- .../flamingock/cloud/CloudAuditStoreImpl.java | 4 +- .../cloud/audit/CloudAuditWriter.java | 17 +------ .../couchbase/internal/CouchbaseAuditor.java | 4 +- .../dynamodb/internal/DynamoDBAuditor.java | 4 +- .../internal/MongoDBReactiveAuditor.java | 4 +- .../sync/internal/MongoDBSyncAuditor.java | 4 +- .../store/sql/internal/SqlAuditor.java | 4 +- .../core/builder/AbstractBuilder.java | 4 +- .../navigator/AuditStoreStepOperations.java | 19 +++++--- .../ChangeProcessStrategyFactory.java | 6 +-- .../store/audit/AuditPersistence.java | 3 +- .../store/audit/LifecycleAuditWriter.java | 44 ------------------- .../pipeline/execution/StageExecutor.java | 6 +-- .../support/inmemory/InMemoryAuditWriter.java | 35 +-------------- .../core/kit/audit/TestAuditWriter.java | 35 ++------------- 16 files changed, 44 insertions(+), 155 deletions(-) delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/LifecycleAuditWriter.java diff --git a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditPersistenceImpl.java b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditPersistenceImpl.java index 6b954ee7d..c120c2693 100644 --- a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditPersistenceImpl.java +++ b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditPersistenceImpl.java @@ -16,6 +16,7 @@ package io.flamingock.cloud; import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.core.audit.issue.AuditEntryIssue; import io.flamingock.internal.common.core.context.ContextContributor; import io.flamingock.internal.util.Result; @@ -24,7 +25,6 @@ import io.flamingock.internal.util.id.ServiceId; import io.flamingock.internal.core.external.store.audit.cloud.CloudAuditPersistence; import io.flamingock.internal.common.core.context.ContextInjectable; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.plan.ExecutionPlanner; import java.util.List; @@ -36,7 +36,7 @@ public final class CloudAuditPersistenceImpl implements CloudAuditPersistence, C private final ServiceId serviceId; - private final LifecycleAuditWriter auditWriter; + private final AuditWriter auditWriter; private final ExecutionPlanner executionPlanner; private final String jwt; @@ -44,7 +44,7 @@ public final class CloudAuditPersistenceImpl implements CloudAuditPersistence, C CloudAuditPersistenceImpl(EnvironmentId environmentId, ServiceId serviceId, String jwt, - LifecycleAuditWriter auditWriter, + AuditWriter auditWriter, ExecutionPlanner executionPlanner, Runnable closer) { this.environmentId =environmentId; diff --git a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditStoreImpl.java b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditStoreImpl.java index bc7788e53..57bb7180f 100644 --- a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditStoreImpl.java +++ b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/CloudAuditStoreImpl.java @@ -15,6 +15,7 @@ */ package io.flamingock.cloud; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.util.Constants; import io.flamingock.internal.util.JsonObjectMapper; import io.flamingock.internal.util.id.RunnerId; @@ -35,7 +36,6 @@ import io.flamingock.cloud.planner.CloudExecutionPlanner; import io.flamingock.cloud.planner.client.ExecutionPlannerClient; import io.flamingock.cloud.planner.client.HttpExecutionPlannerClient; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.targets.TransactionalTargetSystem; import io.flamingock.internal.core.external.targets.TargetSystemManager; import io.flamingock.internal.core.external.targets.mark.TargetSystemAuditMarker; @@ -107,7 +107,7 @@ private CloudAuditPersistenceImpl buildPersistence(RunnerId runnerId, EnvironmentId environmentId = EnvironmentId.fromLong(authResponse.getEnvironmentId()); ServiceId serviceId = ServiceId.fromLong(authResponse.getServiceId()); - LifecycleAuditWriter auditWriter = new HtttpAuditWriter( + AuditWriter auditWriter = new HtttpAuditWriter( cloudConfiguration.getHost(), environmentId, serviceId, diff --git a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/audit/CloudAuditWriter.java b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/audit/CloudAuditWriter.java index 8edc95854..5debc1ae6 100644 --- a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/audit/CloudAuditWriter.java +++ b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/audit/CloudAuditWriter.java @@ -15,25 +15,12 @@ */ package io.flamingock.cloud.audit; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.util.Result; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.store.audit.domain.ExecutionAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.RollbackAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.StartExecutionAuditContextBundle; -public interface CloudAuditWriter extends LifecycleAuditWriter { +public interface CloudAuditWriter extends AuditWriter { - default Result writeStartExecution(StartExecutionAuditContextBundle auditContextBundle) { - return Result.OK();//TODO remove this -// return writeEntry(AuditEntryMapper.map(auditContextBundle)); - } - - - default Result writeExecution(ExecutionAuditContextBundle auditContextBundle) { - return writeEntry(auditContextBundle.toAuditEntry()); - } - - default Result writeRollback(RollbackAuditContextBundle auditContextBundle) { - return writeEntry(auditContextBundle.toAuditEntry()); - } } diff --git a/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/internal/CouchbaseAuditor.java b/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/internal/CouchbaseAuditor.java index 0fc331540..3e9f84248 100644 --- a/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/internal/CouchbaseAuditor.java +++ b/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/internal/CouchbaseAuditor.java @@ -25,10 +25,10 @@ import com.couchbase.client.java.kv.UpsertOptions; import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditReader; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.couchbase.CouchbaseAuditMapper; import io.flamingock.internal.common.couchbase.CouchbaseCollectionHelper; import io.flamingock.internal.common.couchbase.CouchbaseCollectionInitializator; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.util.Result; import io.flamingock.internal.util.log.FlamingockLoggerFactory; import org.slf4j.Logger; @@ -37,7 +37,7 @@ import java.util.stream.Collectors; -public class CouchbaseAuditor implements LifecycleAuditWriter, AuditReader { +public class CouchbaseAuditor implements AuditWriter, AuditReader { private static final Logger logger = FlamingockLoggerFactory.getLogger("CouchbaseAuditor"); diff --git a/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/internal/DynamoDBAuditor.java b/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/internal/DynamoDBAuditor.java index ddaa12e52..0ef4dac8f 100644 --- a/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/internal/DynamoDBAuditor.java +++ b/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/internal/DynamoDBAuditor.java @@ -15,7 +15,7 @@ */ package io.flamingock.store.dynamodb.internal; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.core.external.store.audit.community.CommunityAuditReader; import io.flamingock.internal.util.dynamodb.entities.AuditEntryEntity; import io.flamingock.internal.common.core.audit.AuditEntry; @@ -36,7 +36,7 @@ import static java.util.Collections.emptyList; -public class DynamoDBAuditor implements LifecycleAuditWriter, CommunityAuditReader { +public class DynamoDBAuditor implements AuditWriter, CommunityAuditReader { private static final Logger logger = FlamingockLoggerFactory.getLogger("DynamoAuditor"); diff --git a/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/internal/MongoDBReactiveAuditor.java b/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/internal/MongoDBReactiveAuditor.java index a95f4e0d9..dff532d50 100644 --- a/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/internal/MongoDBReactiveAuditor.java +++ b/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/internal/MongoDBReactiveAuditor.java @@ -25,11 +25,11 @@ import com.mongodb.reactivestreams.client.MongoDatabase; import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditReader; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.mongodb.CollectionInitializator; import io.flamingock.internal.common.mongodb.MongoDBAuditMapper; import io.flamingock.internal.common.mongodb.MongoDBReactiveCollectionHelper; import io.flamingock.internal.common.mongodb.MongoDBDocumentHelper; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.util.Result; import io.flamingock.internal.util.log.FlamingockLoggerFactory; import io.flamingock.reactive.util.PublisherSync; @@ -44,7 +44,7 @@ import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_EXECUTION_ID; import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_STATE; -public class MongoDBReactiveAuditor implements LifecycleAuditWriter, AuditReader { +public class MongoDBReactiveAuditor implements AuditWriter, AuditReader { private static final Logger logger = FlamingockLoggerFactory.getLogger("MongoDBReactiveAuditor"); diff --git a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditor.java b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditor.java index 8d5e6a0cf..f716d904a 100644 --- a/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditor.java +++ b/community/flamingock-mongodb-sync-auditstore/src/main/java/io/flamingock/store/mongodb/sync/internal/MongoDBSyncAuditor.java @@ -25,11 +25,11 @@ import com.mongodb.client.result.UpdateResult; import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditReader; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.mongodb.CollectionInitializator; import io.flamingock.internal.common.mongodb.MongoDBAuditMapper; import io.flamingock.internal.common.mongodb.MongoDBSyncCollectionHelper; import io.flamingock.internal.common.mongodb.MongoDBDocumentHelper; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.util.Result; import io.flamingock.internal.util.log.FlamingockLoggerFactory; import org.bson.Document; @@ -44,7 +44,7 @@ import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_EXECUTION_ID; import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_STATE; -public class MongoDBSyncAuditor implements LifecycleAuditWriter, AuditReader { +public class MongoDBSyncAuditor implements AuditWriter, AuditReader { private static final Logger logger = FlamingockLoggerFactory.getLogger("MongoDBSyncAuditor"); diff --git a/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/internal/SqlAuditor.java b/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/internal/SqlAuditor.java index d39a7d877..eb3df8be8 100644 --- a/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/internal/SqlAuditor.java +++ b/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/internal/SqlAuditor.java @@ -18,9 +18,9 @@ import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditReader; import io.flamingock.internal.common.core.audit.AuditTxType; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.sql.SqlDialect; import io.flamingock.internal.common.sql.dialectHelpers.SqlAuditorDialectHelper; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.util.Result; import javax.sql.DataSource; @@ -28,7 +28,7 @@ import java.util.ArrayList; import java.util.List; -public class SqlAuditor implements LifecycleAuditWriter, AuditReader { +public class SqlAuditor implements AuditWriter, AuditReader { private final DataSource dataSource; private final String auditTableName; diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractBuilder.java index b70d28030..545ab323e 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractBuilder.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractBuilder.java @@ -16,6 +16,7 @@ package io.flamingock.internal.core.builder; import io.flamingock.api.external.TargetSystem; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.core.context.Context; import io.flamingock.internal.common.core.context.ContextConfigurable; import io.flamingock.internal.common.core.context.Dependency; @@ -24,7 +25,6 @@ import io.flamingock.internal.core.context.PriorityContext; import io.flamingock.internal.core.external.store.AuditStore; import io.flamingock.internal.core.external.store.audit.AuditPersistence; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.targets.TargetSystemManager; import io.flamingock.internal.util.log.FlamingockLoggerFactory; import io.flamingock.internal.util.Property; @@ -89,7 +89,7 @@ protected void configureStoreAndTargetSystem(PriorityContext dependencyContext) protected AuditPersistence getAuditPersistence(PriorityContext hierarchicalContext) { AuditPersistence persistence = auditStore.getPersistence(); - hierarchicalContext.addDependency(new Dependency(LifecycleAuditWriter.class, persistence)); + hierarchicalContext.addDependency(new Dependency(AuditWriter.class, persistence)); return persistence; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/AuditStoreStepOperations.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/AuditStoreStepOperations.java index 04385882f..a7ab64ff2 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/AuditStoreStepOperations.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/AuditStoreStepOperations.java @@ -15,8 +15,9 @@ */ package io.flamingock.internal.core.change.navigation.navigator; +import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditTxType; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.core.external.store.audit.domain.ExecutionAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.RollbackAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.RuntimeContext; @@ -32,11 +33,11 @@ public class AuditStoreStepOperations { - private final LifecycleAuditWriter auditWriter; + private final AuditWriter auditWriter; private final AuditTxType auditTxType; private final String targetSystemId; - public AuditStoreStepOperations(LifecycleAuditWriter auditWriter, AuditTxType auditTxType, String targetSystemId) { + public AuditStoreStepOperations(AuditWriter auditWriter, AuditTxType auditTxType, String targetSystemId) { this.auditWriter = auditWriter; this.auditTxType = auditTxType; this.targetSystemId = targetSystemId; @@ -44,22 +45,26 @@ public AuditStoreStepOperations(LifecycleAuditWriter auditWriter, AuditTxType au public Result auditStartExecution(StartStep startStep, ExecutionContext executionContext, LocalDateTime appliedAt) { RuntimeContext runtimeContext = RuntimeContext.builder().setStartStep(startStep).setAppliedAt(appliedAt).build(); - return auditWriter.writeStartExecution(new StartExecutionAuditContextBundle(startStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId)); + AuditEntry auditEntry = new StartExecutionAuditContextBundle(startStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId).toAuditEntry(); + return auditWriter.writeEntry(auditEntry); } public Result auditExecution(ExecutionStep executionStep, ExecutionContext executionContext, LocalDateTime appliedAt) { RuntimeContext runtimeContext = RuntimeContext.builder().setExecutionStep(executionStep).setAppliedAt(appliedAt).build(); - return auditWriter.writeExecution(new ExecutionAuditContextBundle(executionStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId)); + AuditEntry auditEntry = new ExecutionAuditContextBundle(executionStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId).toAuditEntry(); + return auditWriter.writeEntry(auditEntry); } public Result auditManualRollback(ManualRolledBackStep rolledBackStep, ExecutionContext executionContext, LocalDateTime appliedAt) { RuntimeContext runtimeContext = RuntimeContext.builder().setManualRollbackStep(rolledBackStep).setAppliedAt(appliedAt).build(); - return auditWriter.writeRollback(new RollbackAuditContextBundle(rolledBackStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId)); + AuditEntry auditEntry = new RollbackAuditContextBundle(rolledBackStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId).toAuditEntry(); + return auditWriter.writeEntry(auditEntry); } public Result auditAutoRollback(CompleteAutoRolledBackStep rolledBackStep, ExecutionContext executionContext, LocalDateTime appliedAt) { RuntimeContext runtimeContext = RuntimeContext.builder().setAutoRollbackStep(rolledBackStep).setAppliedAt(appliedAt).build(); - return auditWriter.writeRollback(new RollbackAuditContextBundle(rolledBackStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId)); + AuditEntry auditEntry = new RollbackAuditContextBundle(rolledBackStep.getLoadedChange(), executionContext, runtimeContext, auditTxType, targetSystemId).toAuditEntry(); + return auditWriter.writeEntry(auditEntry); } } \ No newline at end of file diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/ChangeProcessStrategyFactory.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/ChangeProcessStrategyFactory.java index 2a7ebbbd3..592cab5c7 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/ChangeProcessStrategyFactory.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/change/navigation/navigator/ChangeProcessStrategyFactory.java @@ -16,9 +16,9 @@ package io.flamingock.internal.core.change.navigation.navigator; import io.flamingock.internal.common.core.audit.AuditTxType; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.core.context.ContextResolver; import io.flamingock.internal.common.core.error.FlamingockException; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.store.lock.Lock; import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; @@ -60,7 +60,7 @@ public class ChangeProcessStrategyFactory { protected ExecutableChange change; - protected LifecycleAuditWriter auditWriter; + protected AuditWriter auditWriter; protected Lock lock; @@ -79,7 +79,7 @@ public ChangeProcessStrategyFactory setChange(ExecutableChange change) { return this; } - public ChangeProcessStrategyFactory setAuditWriter(LifecycleAuditWriter auditWriter) { + public ChangeProcessStrategyFactory setAuditWriter(AuditWriter auditWriter) { this.auditWriter = auditWriter; return this; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/AuditPersistence.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/AuditPersistence.java index 864594bf4..1b27cc2c4 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/AuditPersistence.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/AuditPersistence.java @@ -16,11 +16,12 @@ package io.flamingock.internal.core.external.store.audit; import io.flamingock.internal.common.core.audit.AuditReader; +import io.flamingock.internal.common.core.audit.AuditWriter; import java.util.Collections; import java.util.Set; -public interface AuditPersistence extends LifecycleAuditWriter, AuditReader { +public interface AuditPersistence extends AuditWriter, AuditReader { default Runnable getCloser() { return () -> { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/LifecycleAuditWriter.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/LifecycleAuditWriter.java deleted file mode 100644 index 615814544..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/LifecycleAuditWriter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 Flamingock (https://www.flamingock.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.flamingock.internal.core.external.store.audit; - -import io.flamingock.internal.common.core.audit.AuditWriter; -import io.flamingock.internal.core.external.store.audit.domain.ExecutionAuditContextBundle; -import io.flamingock.internal.core.external.store.audit.domain.RollbackAuditContextBundle; -import io.flamingock.internal.core.external.store.audit.domain.StartExecutionAuditContextBundle; -import io.flamingock.internal.util.Result; - -/** - * This class implements the Facade pattern containing the responsibility to log the changeStep, map it to Entry - * and log then entry just in order to avoid having too many classes to implement that depend on the - * same AuditEntry implementation, which would enforce to add the generic to the holder/orchestrator class. - * However, the `mapper responsibility` is intended to be delegated to a Mapper class, but that's left to decide - * to the developer implementing this abstract class. - */ -public interface LifecycleAuditWriter extends AuditWriter { - default Result writeStartExecution(StartExecutionAuditContextBundle auditContextBundle) { - return writeEntry(auditContextBundle.toAuditEntry()); - } - - default Result writeExecution(ExecutionAuditContextBundle auditContextBundle) { - return writeEntry(auditContextBundle.toAuditEntry()); - } - - default Result writeRollback(RollbackAuditContextBundle auditContextBundle) { - return writeEntry(auditContextBundle.toAuditEntry()); - } - -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java index f5d7c0a16..43331619d 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java @@ -15,12 +15,12 @@ */ package io.flamingock.internal.core.pipeline.execution; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.common.core.context.ContextResolver; import io.flamingock.internal.common.core.context.Dependency; import io.flamingock.internal.common.core.pipeline.StageDescriptor; import io.flamingock.internal.common.core.response.data.StageResult; import io.flamingock.internal.core.context.PriorityContext; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.store.lock.Lock; import io.flamingock.internal.core.external.targets.TargetSystemManager; import io.flamingock.internal.core.operation.result.StageResultBuilder; @@ -44,7 +44,7 @@ public class StageExecutor { private static final Logger logger = FlamingockLoggerFactory.getLogger("StageExecutor"); - protected final LifecycleAuditWriter auditWriter; + protected final AuditWriter auditWriter; private final ContextResolver baseDependencyContext; private final Set> nonGuardedTypes; @@ -53,7 +53,7 @@ public class StageExecutor { public StageExecutor(ContextResolver dependencyContext, Set> nonGuardedTypes, - LifecycleAuditWriter auditWriter, + AuditWriter auditWriter, TargetSystemManager targetSystemManager, TransactionWrapper auditStoreTxWrapper) { this.baseDependencyContext = dependencyContext; diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/inmemory/InMemoryAuditWriter.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/inmemory/InMemoryAuditWriter.java index 3e018c5f3..f05bc92f5 100644 --- a/core/flamingock-test-support/src/main/java/io/flamingock/support/inmemory/InMemoryAuditWriter.java +++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/inmemory/InMemoryAuditWriter.java @@ -16,50 +16,19 @@ package io.flamingock.support.inmemory; import io.flamingock.internal.common.core.audit.AuditEntry; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.core.external.store.audit.domain.ExecutionAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.RollbackAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.StartExecutionAuditContextBundle; import io.flamingock.internal.util.Result; -class InMemoryAuditWriter implements LifecycleAuditWriter { +class InMemoryAuditWriter implements AuditWriter { private final InMemoryAuditStorage auditStorage; public InMemoryAuditWriter(InMemoryAuditStorage auditStorage) { this.auditStorage = auditStorage; } - - @Override - public Result writeStartExecution(StartExecutionAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } - - @Override - public Result writeExecution(ExecutionAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } - - @Override - public Result writeRollback(RollbackAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } - /** * Write an audit entry to the storage */ diff --git a/utils/test-util/src/main/java/io/flamingock/core/kit/audit/TestAuditWriter.java b/utils/test-util/src/main/java/io/flamingock/core/kit/audit/TestAuditWriter.java index b7e05c49f..1833890dc 100644 --- a/utils/test-util/src/main/java/io/flamingock/core/kit/audit/TestAuditWriter.java +++ b/utils/test-util/src/main/java/io/flamingock/core/kit/audit/TestAuditWriter.java @@ -16,49 +16,20 @@ package io.flamingock.core.kit.audit; import io.flamingock.internal.common.core.audit.AuditEntry; -import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; +import io.flamingock.internal.common.core.audit.AuditWriter; import io.flamingock.internal.core.external.store.audit.domain.ExecutionAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.RollbackAuditContextBundle; import io.flamingock.internal.core.external.store.audit.domain.StartExecutionAuditContextBundle; import io.flamingock.internal.util.Result; -public class TestAuditWriter implements LifecycleAuditWriter { +public class TestAuditWriter implements AuditWriter { private final AuditStorage auditStorage; public TestAuditWriter(AuditStorage auditStorage) { this.auditStorage = auditStorage; } - - @Override - public Result writeStartExecution(StartExecutionAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } - - @Override - public Result writeExecution(ExecutionAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } - - @Override - public Result writeRollback(RollbackAuditContextBundle auditContextBundle) { - try { - AuditEntry auditEntry = auditContextBundle.toAuditEntry(); - return writeEntry(auditEntry); - } catch (Exception e) { - return new Result.Error(e); - } - } + /** * Write an audit entry to the storage From 00a67e0b1268c74cae95e28123f9a35f77f8d27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Wed, 22 Jul 2026 22:49:02 +0200 Subject: [PATCH 6/7] chore: license header --- .../common/core/journal/JournalEvent.java | 1 - .../transaction/TransactionalExternalSystem.java | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java index 884c3a52f..fbef90afa 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/journal/JournalEvent.java @@ -1,4 +1,3 @@ - /* * Copyright 2026 Flamingock (https://www.flamingock.io) * diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java index 4440a457f..afa938aa4 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/transaction/TransactionalExternalSystem.java @@ -1,3 +1,18 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.flamingock.internal.common.core.transaction; import io.flamingock.api.external.ExternalSystem; From f525466b78044675a2498920ed7f14abb669e363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A9rez?= Date: Thu, 23 Jul 2026 00:57:11 +0200 Subject: [PATCH 7/7] refactor: remove OpsClient --- .../internal/core/builder/OpsClient.java | 123 ----- .../core/builder/OpsClientBuilder.java | 440 ------------------ 2 files changed, 563 deletions(-) delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClient.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClientBuilder.java diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClient.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClient.java deleted file mode 100644 index c86e1775c..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClient.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2025 Flamingock (https://www.flamingock.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.flamingock.internal.core.builder; - -import io.flamingock.internal.common.core.audit.AuditEntry; -import io.flamingock.internal.common.core.audit.AuditHistoryReader; -import io.flamingock.internal.common.core.audit.AuditIssueResolver; -import io.flamingock.internal.common.core.audit.AuditSnapshotReader; -import io.flamingock.internal.common.core.audit.issue.AuditEntryIssue; -import io.flamingock.internal.common.core.recovery.FixResult; -import io.flamingock.internal.common.core.recovery.Resolution; -import io.flamingock.internal.core.plan.ExecutionId; -import io.flamingock.internal.core.external.store.audit.AuditPersistence; -import io.flamingock.internal.util.StringUtil; -import io.flamingock.internal.util.id.RunnerId; -import io.flamingock.internal.util.log.FlamingockLoggerFactory; -import org.slf4j.Logger; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -public class OpsClient implements AuditSnapshotReader, AuditHistoryReader, AuditIssueResolver { - private final Logger logger = FlamingockLoggerFactory.getLogger("OpsClient"); - - private final AuditPersistence auditPersistence; - - OpsClient(RunnerId runnerId, AuditPersistence auditPersistence) { - this.auditPersistence = auditPersistence; - } - - public List getAuditHistory() { - logger.debug("Getting full audit history"); - return auditPersistence.getAuditHistory(); - } - - @Override - public List getAuditSnapshot() { - logger.debug("Getting audit entries snapshot (latest per change)"); - return auditPersistence.getAuditSnapshot(); - } - - @Override - public List getAuditSnapshotSince(LocalDateTime since) { - logger.debug("Getting audit entries since: {}", since); - return auditPersistence.getAuditSnapshot() - .stream() - .filter(auditEntry -> !auditEntry.getCreatedAt().isBefore(since)) - .collect(Collectors.toList()); - } - - @Override - public Optional getAuditIssueByChangeId(String changeId) { - logger.debug("Getting issue details for changeId: {}", changeId); - return auditPersistence.getAuditIssueByChangeId(changeId); - } - - @Override - public List getAuditIssues() { - logger.debug("Getting audit entries with issues"); - return auditPersistence.getAuditIssues(); - } - - @Override - // TODO: This needs to be done under the lok - public FixResult fixAuditIssue(String changeId, Resolution resolution) { - logger.debug("Change[{}] marked as {}", changeId, resolution); - - Optional auditIssue = getAuditIssueByChangeId(changeId); - if (!auditIssue.isPresent()) { - return FixResult.NO_ISSUE_FOUND; - } - - AuditEntry currentEntry = auditIssue.get().getAuditEntry(); - AuditEntry fixedAuditEntry = new AuditEntry( - ExecutionId.getNewExecutionId(), - currentEntry.getStageId(), - currentEntry.getChangeId(), - "flamingock-cli",//TODO in cloud this will be retrieved from the token - LocalDateTime.now(), - getState(resolution), - currentEntry.getType(), - currentEntry.getClassName(), - currentEntry.getMethodName(), - null, //TODO: set sourceFile - currentEntry.getExecutionMillis(), - StringUtil.hostname(), - currentEntry.getMetadata(),//?? - currentEntry.getSystemChange(), - "", - currentEntry.getTxType(), - currentEntry.getTargetSystemId(), - currentEntry.getOrder(), - currentEntry.getRecoveryStrategy(), - currentEntry.getTransactionFlag() - ); - auditPersistence.writeEntry(fixedAuditEntry); - return FixResult.APPLIED; - } - - private AuditEntry.Status getState(Resolution resolution) { - if(resolution == Resolution.APPLIED) { - return AuditEntry.Status.MANUAL_MARKED_AS_APPLIED; - } else { - // Resolution.ROLLED_BACK - return AuditEntry.Status.MANUAL_MARKED_AS_ROLLED_BACK; - } - } -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClientBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClientBuilder.java deleted file mode 100644 index 7a357438c..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/OpsClientBuilder.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright 2023 Flamingock (https://www.flamingock.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.flamingock.internal.core.builder; - -import io.flamingock.internal.common.core.context.Context; -import io.flamingock.internal.common.core.context.Dependency; -import io.flamingock.internal.core.configuration.core.CoreConfiguration; -import io.flamingock.internal.core.context.PriorityContext; -import io.flamingock.internal.core.context.SimpleContext; -import io.flamingock.internal.core.external.store.AuditStore; -import io.flamingock.internal.core.external.store.audit.AuditPersistence; -import io.flamingock.internal.util.log.FlamingockLoggerFactory; -import io.flamingock.internal.util.Property; -import io.flamingock.internal.util.id.RunnerId; -import org.slf4j.Logger; - -import java.io.File; -import java.net.InetAddress; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Period; -import java.time.ZonedDateTime; -import java.util.Currency; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -public class OpsClientBuilder - extends AbstractBuilder, OpsClientBuilder> { - private static final Logger logger = FlamingockLoggerFactory.getLogger("Builder"); - - /////////////////////////////////////////////////////////////////////////////////// - // BUILD - - /// //////////////////////////////////////////////////////////////////////////////// - - public OpsClientBuilder( - CoreConfiguration coreConfiguration, - Context context, - AuditStore auditStore) { - super(coreConfiguration, context, auditStore); - } - - protected OpsClientBuilder getSelf() { - return this; - } - - public OpsClient build() { - validateAuditStore(); - RunnerId runnerId = generateRunnerId(); - PriorityContext dependencyContext = buildContext(); - configureStoreAndTargetSystem(dependencyContext); - targetSystemManager.initialize(dependencyContext); - AuditPersistence persistence = getAuditPersistence(dependencyContext); - return new OpsClient(runnerId, persistence); - } - - private PriorityContext buildContext() { - logger.trace("injecting internal configuration"); - addDependency(coreConfiguration); - return new PriorityContext(new SimpleContext(), context); - } - - - - /////////////////////////////////////////////////////////////////////////////////// - // CORE - - /// //////////////////////////////////////////////////////////////////////////////// - - - @Override - public OpsClientBuilder setLockAcquiredForMillis(long lockAcquiredForMillis) { - coreConfiguration.setLockAcquiredForMillis(lockAcquiredForMillis); - return getSelf(); - } - - @Override - public OpsClientBuilder setLockQuitTryingAfterMillis(Long lockQuitTryingAfterMillis) { - coreConfiguration.setLockQuitTryingAfterMillis(lockQuitTryingAfterMillis); - return getSelf(); - } - - @Override - public OpsClientBuilder setLockTryFrequencyMillis(long lockTryFrequencyMillis) { - coreConfiguration.setLockTryFrequencyMillis(lockTryFrequencyMillis); - return getSelf(); - } - - @Override - public OpsClientBuilder setThrowExceptionIfCannotObtainLock(boolean throwExceptionIfCannotObtainLock) { - coreConfiguration.setThrowExceptionIfCannotObtainLock(throwExceptionIfCannotObtainLock); - return getSelf(); - } - - @Override - public OpsClientBuilder setEnabled(boolean enabled) { - coreConfiguration.setEnabled(enabled); - return getSelf(); - } - - @Override - public OpsClientBuilder setServiceIdentifier(String serviceIdentifier) { - coreConfiguration.setServiceIdentifier(serviceIdentifier); - return getSelf(); - } - - @Override - public OpsClientBuilder setMetadata(Map metadata) { - coreConfiguration.setMetadata(metadata); - return getSelf(); - } - @Override - public long getLockAcquiredForMillis() { - return coreConfiguration.getLockAcquiredForMillis(); - } - - @Override - public Long getLockQuitTryingAfterMillis() { - return coreConfiguration.getLockQuitTryingAfterMillis(); - } - - @Override - public long getLockTryFrequencyMillis() { - return coreConfiguration.getLockTryFrequencyMillis(); - } - - @Override - public boolean isThrowExceptionIfCannotObtainLock() { - return coreConfiguration.isThrowExceptionIfCannotObtainLock(); - } - - @Override - public boolean isEnabled() { - return coreConfiguration.isEnabled(); - } - - @Override - public String getServiceIdentifier() { - return coreConfiguration.getServiceIdentifier(); - } - - @Override - public Map getMetadata() { - return coreConfiguration.getMetadata(); - } - - - /////////////////////////////////////////////////////////////////////////////////// - // STANDALONE - - /// //////////////////////////////////////////////////////////////////////////////// - - - @Override - public OpsClientBuilder addDependency(String name, Class type, Object instance) { - context.addDependency(new Dependency(name, type, instance)); - return getSelf(); - } - - @Override - public OpsClientBuilder addDependency(Object instance) { - if (instance instanceof Dependency) { - context.addDependency(instance); - return getSelf(); - } else { - return addDependency(Dependency.DEFAULT_NAME, instance.getClass(), instance); - } - - } - - @Override - public OpsClientBuilder addDependency(String name, Object instance) { - return addDependency(name, instance.getClass(), instance); - } - - @Override - public OpsClientBuilder addDependency(Class type, Object instance) { - return addDependency(Dependency.DEFAULT_NAME, type, instance); - } - - @Override - public OpsClientBuilder setProperty(Property property) { - context.setProperty(property); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, String value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Boolean value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Integer value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Float value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Long value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Double value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, UUID value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Currency value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Locale value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Charset value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, File value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Path value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, InetAddress value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, URL value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, URI value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Duration value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Period value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Instant value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, LocalDate value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, LocalTime value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, LocalDateTime value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, ZonedDateTime value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, OffsetDateTime value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, OffsetTime value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, java.util.Date value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, java.sql.Date value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Time value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Timestamp value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, String[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Integer[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Long[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Double[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Float[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Boolean[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Byte[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Short[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public OpsClientBuilder setProperty(String key, Character[] value) { - context.setProperty(key, value); - return getSelf(); - } - - @Override - public > OpsClientBuilder setProperty(String key, T value) { - context.setProperty(key, value); - return getSelf(); - } - -}