Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ public static CouchbaseAuditStore from(CouchbaseExternalSystem targetSystem) {
return new CouchbaseAuditStore(targetSystem);
}

/**
* {@inheritDoc}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ public static DynamoDBAuditStore from(DynamoDBExternalSystem targetSystem) {
return new DynamoDBAuditStore(targetSystem);
}

/**
* {@inheritDoc}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ public static MongoDBReactiveAuditStore from(MongoDBReactiveExternalSystem targe
return new MongoDBReactiveAuditStore(targetSystem);
}

/**
* {@inheritDoc}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ public static MongoDBSyncAuditStore from(MongoDBExternalSystem targetSystem) {
return new MongoDBSyncAuditStore(targetSystem);
}

/**
* {@inheritDoc}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ public static SqlAuditStore from(SqlExternalSystem targetSystem) {
return new SqlAuditStore(targetSystem);
}

/**
* {@inheritDoc}
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* <p>
* 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@

public interface AuditStore<PERSISTENCE extends AuditPersistence> extends ExternalSystem, ContextInitializable {

/**
* Returns the external system this audit store was built from.
* <p>
* Not to be confused with {@link #getId()}, which this store inherits from {@code ExternalSystem} and
* which identifies <em>the store</em> (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.
* <p>
* 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.
* <p>
* 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)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -50,6 +52,12 @@ public class TargetSystemManager implements ContextInitializable {
//TODO if we decide to remove the TX_SHARED
private AbstractTargetSystem<?> auditStoreTargetSystem;
private final Map<String, AbstractTargetSystem<?>> targetSystemMap = new HashMap<>();
/**
* Reserved id to real id. Deliberately an indirection rather than extra {@link #targetSystemMap}
* entries: {@link #initialize} and {@link #getTransactionalTargetSystems} both iterate that map's
* values, so a second entry pointing at the same instance would initialize it twice and count it twice.
*/
private final Map<String, String> aliases = new HashMap<>();

@Override
public void initialize(ContextResolver contextResolver) {
Expand All @@ -67,6 +75,58 @@ public void initialize(ContextResolver contextResolver) {
* @param targetSystem the target system to register (must be non-null and have a non-empty ID)
* @throws IllegalArgumentException if the target system or its ID is null/blank
*/
/**
* Registers the audit store's own target system under the reserved
* {@link PipelineHelper#AUDIT_STORE_TARGET_SYSTEM} id.
* <p>
* 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.
* <p>
* Registers the <em>instance</em>, 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.
* <p>
* 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));
}
Expand All @@ -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(
Expand Down Expand Up @@ -125,7 +188,7 @@ public List<TransactionalTargetSystem<?>> getTransactionalTargetSystems() {
* @param targetSystem the target system to validate
* @throws IllegalArgumentException if validation fails
*/
private AbstractTargetSystem<?> validateAndCast(TargetSystem targetSystem) {
private AbstractTargetSystem<?> validateAndCast(ExternalSystem targetSystem) {
if (targetSystem == null) {
throw new IllegalArgumentException("Target system null not allowed");
}
Expand Down
Loading
Loading