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 57bb7180f..19ab1f30b 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 @@ -49,11 +49,32 @@ import java.io.IOException; import java.util.List; import java.util.stream.Collectors; +import io.flamingock.api.external.ExternalSystem; +import io.flamingock.targetsystem.nontransactional.NonTransactionalTargetSystem; public class CloudAuditStoreImpl implements CloudAuditStore { private static final Logger logger = FlamingockLoggerFactory.getLogger("CloudAuditStore"); private CloudAuditPersistenceImpl persistence; + /** Lazily created stand-in; see {@link #getExternalSystem()}. */ + private NonTransactionalTargetSystem externalSystem; + + /** + * {@inheritDoc} + *
+ * This store has no external system of its own - it keeps everything in Flamingock Cloud. A + * non-transactional stand-in is returned rather than {@code null} so that the reserved + * {@code AUDIT_STORE_TARGET_SYSTEM} id still resolves, letting system changes run against this store. + * Cached because the identity of the instance matters once it is registered. + */ + @Override + public synchronized ExternalSystem getExternalSystem() { + if (externalSystem == null) { + externalSystem = new NonTransactionalTargetSystem(getId()); + } + return externalSystem; + } + @Override public String getId() { return Constants.DEFAULT_CLOUD_AUDIT_STORE; diff --git a/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/CouchbaseAuditStore.java b/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/CouchbaseAuditStore.java index a4c0765a8..ccf7530b8 100644 --- a/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/CouchbaseAuditStore.java +++ b/community/flamingock-couchbase-auditstore/src/main/java/io/flamingock/store/couchbase/CouchbaseAuditStore.java @@ -89,6 +89,17 @@ public static CouchbaseAuditStore from(CouchbaseExternalSystem targetSystem) { return new CouchbaseAuditStore(targetSystem); } + /** + * {@inheritDoc} + *
+ * The system this store was handed by {@code from(...)} - not to be confused with + * {@link #getId()}, which names the store itself. + */ + @Override + public CouchbaseExternalSystem getExternalSystem() { + return targetSystem; + } + @Override public String getId() { return Constants.DEFAULT_COUCHBASE_AUDIT_STORE; diff --git a/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/DynamoDBAuditStore.java b/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/DynamoDBAuditStore.java index 7ec12203d..313022199 100644 --- a/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/DynamoDBAuditStore.java +++ b/community/flamingock-dynamodb-auditstore/src/main/java/io/flamingock/store/dynamodb/DynamoDBAuditStore.java @@ -77,6 +77,17 @@ public static DynamoDBAuditStore from(DynamoDBExternalSystem targetSystem) { return new DynamoDBAuditStore(targetSystem); } + /** + * {@inheritDoc} + *
+ * The system this store was handed by {@code from(...)} - not to be confused with + * {@link #getId()}, which names the store itself. + */ + @Override + public DynamoDBExternalSystem getExternalSystem() { + return targetSystem; + } + @Override public String getId() { return Constants.DEFAULT_DYNAMODB_AUDIT_STORE; diff --git a/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/MongoDBReactiveAuditStore.java b/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/MongoDBReactiveAuditStore.java index 22f986fc6..bf4b722ee 100644 --- a/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/MongoDBReactiveAuditStore.java +++ b/community/flamingock-mongodb-reactive-auditstore/src/main/java/io/flamingock/store/mongodb/reactive/MongoDBReactiveAuditStore.java @@ -90,6 +90,17 @@ public static MongoDBReactiveAuditStore from(MongoDBReactiveExternalSystem targe return new MongoDBReactiveAuditStore(targetSystem); } + /** + * {@inheritDoc} + *
+ * The system this store was handed by {@code from(...)} - not to be confused with + * {@link #getId()}, which names the store itself. + */ + @Override + public MongoDBReactiveExternalSystem getExternalSystem() { + return mongoDBTargetSystem; + } + @Override public String getId() { return Constants.DEFAULT_MONGODB_AUDIT_STORE; 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 080d6392f..bb65ff274 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 @@ -95,6 +95,17 @@ public static MongoDBSyncAuditStore from(MongoDBExternalSystem targetSystem) { return new MongoDBSyncAuditStore(targetSystem); } + /** + * {@inheritDoc} + *
+ * The system this store was handed by {@code from(...)} - not to be confused with + * {@link #getId()}, which names the store itself. + */ + @Override + public MongoDBExternalSystem getExternalSystem() { + return mongoDBTargetSystem; + } + @Override public String getId() { return Constants.DEFAULT_MONGODB_AUDIT_STORE; diff --git a/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/SqlAuditStore.java b/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/SqlAuditStore.java index 13083872c..ce40a1ebb 100644 --- a/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/SqlAuditStore.java +++ b/community/flamingock-sql-auditstore/src/main/java/io/flamingock/store/sql/SqlAuditStore.java @@ -76,6 +76,17 @@ public static SqlAuditStore from(SqlExternalSystem targetSystem) { return new SqlAuditStore(targetSystem); } + /** + * {@inheritDoc} + *
+ * The system this store was handed by {@code from(...)} - not to be confused with + * {@link #getId()}, which names the store itself. + */ + @Override + public SqlExternalSystem getExternalSystem() { + return targetSystem; + } + @Override public String getId() { return Constants.DEFAULT_SQL_AUDIT_STORE; diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/pipeline/PipelineHelper.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/pipeline/PipelineHelper.java index d03a46d95..424ec70e4 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/pipeline/PipelineHelper.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/pipeline/PipelineHelper.java @@ -24,6 +24,22 @@ public class PipelineHelper { public static final String SYSTEM_STAGE_ID = "flamingock-system-stage"; public static final String LEGACY_STAGE_ID = "flamingock-legacy-stage"; + /** + * Reserved target-system id resolving to whatever target system the audit store was built from. + *
+ * System changes that operate on the audit store itself - compaction, journal backfill - have no target + * system of their own to name, yet every change is resolved against one + * ({@code ChangeProcessStrategyFactory} asks {@code TargetSystemManager}, which throws on an unknown + * id). Declaring this id lets them reach the audit store's own target system without the user having to + * name it, and without the framework inventing a default. + *
+ * Lives here rather than in {@code io.flamingock.internal.util.Constants} because that class ships from
+ * the external {@code flamingock-general-util} artifact, so adding to it needs a cross-repo release.
+ * This module is reachable from both the annotation processor and the runtime, which is what a reserved
+ * id needs.
+ */
+ public static final String AUDIT_STORE_TARGET_SYSTEM = "flamingock-audit-store-target-system";
+
private final PipelineDescriptor pipelineDescriptor;
public PipelineHelper(PipelineDescriptor pipelineDescriptor) {
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 88b184090..ebe1258dd 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
@@ -82,6 +82,12 @@ protected AbstractBuilder(
protected abstract HOLDER getSelf();
protected void configureStoreAndTargetSystem(PriorityContext dependencyContext) {
+ // Before initialize(), and that order is load-bearing on two counts: the manager rejects
+ // registrations once initialized, and registering now is what gets the audit store's target system
+ // initialized along with the rest when the user never added it themselves. Safe this early because
+ // getExternalSystem() only hands back a constructor field - the audit store itself need not be
+ // initialized yet.
+ targetSystemManager.registerAuditStoreTargetSystem(auditStore.getExternalSystem());
//remove this, targetSystem should be mandatory
targetSystemManager.initialize(dependencyContext);
auditStore.initialize(dependencyContext);
diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/AuditStore.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/AuditStore.java
index dfb2f2dda..3d82401fa 100644
--- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/AuditStore.java
+++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/AuditStore.java
@@ -29,6 +29,23 @@
public interface AuditStore
+ * Not to be confused with {@link #getId()}, which this store inherits from {@code ExternalSystem} and
+ * which identifies the store (for example {@code mongodb-audit-store}). This returns the
+ * system the store's data actually lives in - the one a {@code from(...)} factory was handed.
+ *
+ * Used to register the reserved {@code PipelineHelper.AUDIT_STORE_TARGET_SYSTEM} id, so that system
+ * changes operating on the audit store can be resolved against a real target system.
+ *
+ * Abstract rather than defaulted: a store that genuinely has no external system must say so by
+ * returning a non-transactional stand-in, not by inheriting a silent {@code null}.
+ *
+ * @return the external system backing this store; never {@code null}
+ */
+ ExternalSystem getExternalSystem();
+
@Deprecated
default PERSISTENCE getPersistence() {
throw new IllegalStateException("Audit stores must provide persistence through getPersistenceFactory(stageId)");
diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TargetSystemManager.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TargetSystemManager.java
index a1e472cda..d2e74136d 100644
--- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TargetSystemManager.java
+++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/targets/TargetSystemManager.java
@@ -15,7 +15,9 @@
*/
package io.flamingock.internal.core.external.targets;
+import io.flamingock.api.external.ExternalSystem;
import io.flamingock.api.external.TargetSystem;
+import io.flamingock.internal.common.core.pipeline.PipelineHelper;
import io.flamingock.internal.common.core.context.ContextInitializable;
import io.flamingock.internal.common.core.context.ContextResolver;
import io.flamingock.internal.common.core.error.FlamingockException;
@@ -50,6 +52,12 @@ public class TargetSystemManager implements ContextInitializable {
//TODO if we decide to remove the TX_SHARED
private AbstractTargetSystem> auditStoreTargetSystem;
private final Map
+ * System changes that act on the audit store - compaction, journal backfill - have no target system of
+ * their own to name, but every change is resolved against one. Declaring the reserved id lets them
+ * reach the store's system without the user naming it.
+ *
+ * Registers the instance, not a name to look up later. That matters: an audit store built with
+ * {@code from(targetSystem)} does not require that target system to have been added to the builder, so
+ * resolving by id alone could find nothing - or worse, find a different instance that happens to share
+ * the id.
+ *
+ * Must be called before {@link #initialize}: {@link #validateAndCast} rejects late registrations, and
+ * registering earlier is also what gets an otherwise-unregistered system initialized along with the
+ * rest.
+ *
+ * @param externalSystem the system the audit store was built from
+ * @throws IllegalArgumentException if it is not an {@link AbstractTargetSystem}, has a blank id, or
+ * this manager is already initialized
+ */
+ public void registerAuditStoreTargetSystem(ExternalSystem externalSystem) {
+ if (externalSystem == null) {
+ // Checked here rather than left to validateAndCast, whose "Target system null not allowed"
+ // says nothing about where the null came from. Every audit store must return a system - one
+ // that keeps no data of its own returns a NonTransactionalTargetSystem stand-in.
+ throw new IllegalArgumentException("The audit store returned no external system. "
+ + "AuditStore.getExternalSystem() must never return null; an audit store with no "
+ + "external system of its own should return a NonTransactionalTargetSystem instead.");
+ }
+ AbstractTargetSystem> targetSystem = validateAndCast(externalSystem);
+ String id = targetSystem.getId();
+
+ AbstractTargetSystem> alreadyRegistered = targetSystemMap.get(id);
+ if (alreadyRegistered == null) {
+ // The user never registered it, so register it here - otherwise it would neither resolve nor
+ // be initialized.
+ targetSystemMap.put(id, targetSystem);
+ } else if (alreadyRegistered != targetSystem) {
+ // Identity, not equality: the user's instance wins, because overwriting it would silently
+ // change what their own changes run against. But the audit store then operates against a
+ // different instance than it was built with, which is worth saying out loud.
+ logger.warn("The audit store was built from a target system with id[{}], but a different "
+ + "instance is already registered under that id. The registered one will be used to "
+ + "resolve [{}]", id, PipelineHelper.AUDIT_STORE_TARGET_SYSTEM);
+ }
+
+ aliases.put(PipelineHelper.AUDIT_STORE_TARGET_SYSTEM, id);
+ logger.debug("Registered [{}] as an alias of target system [{}]",
+ PipelineHelper.AUDIT_STORE_TARGET_SYSTEM, id);
+ }
+
public void add(TargetSystem targetSystem) {
targetSystemMap.put(targetSystem.getId(), validateAndCast(targetSystem));
}
@@ -93,7 +153,10 @@ public TargetSystemOps getTargetSystem(TargetSystemDescriptor tsd) {
*/
public TargetSystemOps getTargetSystem(String id) {
logger.debug("Resolving target system with id: [{}]", id);
-
+
+ // A reserved alias resolves to the id it points at; anything else passes straight through.
+ id = aliases.getOrDefault(id, id);
+
if (id == null || !targetSystemMap.containsKey(id)) {
String availableTargetSystems = String.join(", ", targetSystemMap.keySet());
String message = String.format(
@@ -125,7 +188,7 @@ public List
+ * This store has no external system of its own - it keeps everything in memory. A
+ * non-transactional stand-in is returned rather than {@code null} so that the reserved
+ * {@code AUDIT_STORE_TARGET_SYSTEM} id still resolves, letting system changes run against this store.
+ * Cached because the identity of the instance matters once it is registered.
+ */
+ @Override
+ public synchronized ExternalSystem getExternalSystem() {
+ if (externalSystem == null) {
+ externalSystem = new NonTransactionalTargetSystem(getId());
+ }
+ return externalSystem;
+ }
+
@Override
public String getId() {
return Constants.DEFAULT_IN_MEMORY_AUDIT_STORE;
diff --git a/platform-plugins/flamingock-springboot-integration/build.gradle.kts b/platform-plugins/flamingock-springboot-integration/build.gradle.kts
index 70cf0bb56..bad207d9d 100644
--- a/platform-plugins/flamingock-springboot-integration/build.gradle.kts
+++ b/platform-plugins/flamingock-springboot-integration/build.gradle.kts
@@ -3,6 +3,7 @@ val springBootVersion = "2.0.0.RELEASE"
val springFrameworkVersion = "5.0.4.RELEASE"
dependencies {
api(project(":core:flamingock-core"))
+ testImplementation(project(":core:target-systems:flamingock-nontransactional-targetsystem"))
compileOnlyApi("org.springframework:spring-context:${springFrameworkVersion}")
compileOnlyApi("org.springframework.boot:spring-boot:${springBootVersion}")
compileOnlyApi("org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}")
diff --git a/platform-plugins/flamingock-springboot-integration/src/test/java/io/flamingock/springboot/FlamingockAutoConfigurationTests.java b/platform-plugins/flamingock-springboot-integration/src/test/java/io/flamingock/springboot/FlamingockAutoConfigurationTests.java
index d1ee778b0..ac491c0a5 100644
--- a/platform-plugins/flamingock-springboot-integration/src/test/java/io/flamingock/springboot/FlamingockAutoConfigurationTests.java
+++ b/platform-plugins/flamingock-springboot-integration/src/test/java/io/flamingock/springboot/FlamingockAutoConfigurationTests.java
@@ -28,6 +28,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import io.flamingock.targetsystem.nontransactional.NonTransactionalTargetSystem;
class FlamingockAutoConfigurationTests {
@@ -115,7 +117,12 @@ public List
+ * This store has no external system of its own - it keeps everything in memory. A
+ * non-transactional stand-in is returned rather than {@code null} so that the reserved
+ * {@code AUDIT_STORE_TARGET_SYSTEM} id still resolves, letting system changes run against this store.
+ * Cached because the identity of the instance matters once it is registered.
+ */
+ @Override
+ public synchronized ExternalSystem getExternalSystem() {
+ if (externalSystem == null) {
+ externalSystem = new NonTransactionalTargetSystem(getId());
+ }
+ return externalSystem;
+ }
+
@Override
public String getId() {
return Constants.DEFAULT_IN_MEMORY_AUDIT_STORE;