Last active
February 19, 2025 03:41
-
-
Save hrstoyanov/55c57c824cf3c46a90be497d39df9240 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Provides a context for managing persistence operations within an EclipseStore environment. | |
* This class encapsulates transaction management (commit/rollback), entity creation with | |
* versioning and logging, and interaction with the underlying storage manager. It extends | |
* {@link LockScope} to ensure thread safety for persistence operations. | |
*/ | |
public final class EclipseStoreContext extends LockScope implements PersistenceStoring { | |
/** | |
* The logger for this class. | |
*/ | |
private static final System.Logger LOGGER = System.getLogger(EclipseStoreContext.class.getName()); | |
/** | |
* Entity logger for reports of entity creation and updates. | |
*/ | |
private static final EntityLogger ES_LOGGER = new EntityLogger(){ | |
@Override | |
public void entityCreated(Entity identity, final Entity data) { | |
LOGGER.log(DEBUG, ()-> logMessages("Created", identity)); | |
} | |
@Override | |
public void afterUpdate(Entity identity, final Entity data, final boolean successful) { | |
LOGGER.log(DEBUG, ()-> logMessages(STR."Updated (\{successful})", identity)); | |
} | |
}; | |
/** | |
* The underlying persistence storage implementation. | |
*/ | |
private final PersistenceStoring persistenceStoring; | |
/** | |
* A set containing the values that are scheduled to be stored. Using an IdentityHashMap | |
* ensures that object identity (===) is used for comparisons, rather than equals(). | |
*/ | |
private final Set<Object> valuesToStore = Collections.newSetFromMap(new IdentityHashMap<>()); | |
/** | |
* A flag indicating whether the current transaction should be rolled back. | |
*/ | |
private volatile boolean rollbackOnly = false; | |
/** | |
* A supplier for generating unique Time-Sorted IDs (TSIDs). | |
*/ | |
private final Supplier<Tsid> tsidGenerator; | |
/** | |
* Entity version cleaner. | |
*/ | |
private final EntityVersionCleaner<Integer> cleaner ; | |
/** | |
* Constructs a new EclipseStoreContext. | |
* | |
* @param persistenceStoring The underlying persistence storage implementation. If null, a dummy | |
* implementation is used. | |
* @param tsidGenerator The supplier for generating TSIDs. If null, a default generator is used. | |
*/ | |
public EclipseStoreContext(PersistenceStoring persistenceStoring, int maxPreservedVersion, Supplier<Tsid> tsidGenerator) { | |
this.cleaner = EntityVersionCleaner.AmountPreserving(maxPreservedVersion); | |
this.persistenceStoring = persistenceStoring == null ? DUMMY_PERSISTENCE_STORING_INSTANCE: persistenceStoring; | |
this.tsidGenerator = tsidGenerator==null ? Tsid::fast /*TsidCreator::getTsid*/ : tsidGenerator; | |
} | |
/** | |
* Generates a new, unique Time-Sorted ID (TSID). | |
* | |
* @return The generated TSID. | |
*/ | |
public Tsid generateTsid() { | |
return tsidGenerator.get(); | |
} | |
/** | |
* Creates an entity creator with versioning and logging capabilities. | |
* | |
* @param creator The original entity creator. | |
* @param <E> The type of the entity. | |
* @param <C> The type of the entity creator. | |
* @return An enhanced entity creator with added layers for logging and version control. | |
*/ | |
public <E extends Entity, C extends Entity.Creator<E, C>> C createEntityWithVersioningAndLogging(final C creator) { | |
return creator | |
.addLayer(ES_LOGGER) | |
.addLayer(EntityVersionContext.AutoIncrementingInt(cleaner)); | |
} | |
/** | |
* Creates an entity creator with logging capabilities. | |
* | |
* @param creator The original entity creator. | |
* @param <E> The type of the entity. | |
* @param <C> The type of the entity creator. | |
* @return An enhanced entity creator with an added layer for logging. | |
*/ | |
public <E extends Entity, C extends Entity.Creator<E, C>> C createEntityWithLogging(final C creator) { | |
return creator | |
.addLayer(ES_LOGGER); | |
} | |
/** | |
* Gets the underlying persistence storage implementation. | |
* | |
* @return The persistence storing instance. | |
*/ | |
public PersistenceStoring getPersistenceStoring() { | |
return persistenceStoring; | |
} | |
/** | |
* Gets the embedded storage manager, if the underlying persistence storing implementation | |
* is an instance of {@link EmbeddedStorageManager}. | |
* | |
* @return An {@link Optional} containing the {@link EmbeddedStorageManager}, or an empty | |
* {@link Optional} if the underlying implementation is not an {@link EmbeddedStorageManager}. | |
*/ | |
public Optional<EmbeddedStorageManager> getEmbeddedStorageManager() { | |
return persistenceStoring instanceof EmbeddedStorageManager esm? Optional.of(esm): Optional.empty(); | |
} | |
/** | |
* Returns a list of values that are scheduled to be stored. | |
* | |
* @return A list of objects to be stored. | |
*/ | |
public List<Object> getValuesToStore() { | |
return List.of(valuesToStore); | |
} | |
/** | |
* Checks if a specific object is scheduled to be stored. | |
* | |
* @param o The object to check. | |
* @return {@code true} if the object is scheduled to be stored, {@code false} otherwise. | |
*/ | |
public boolean willStore(Object o){ | |
if(isRollbackOnly()) return false; | |
return read(()->valuesToStore.contains(o)); | |
} | |
/** | |
* Checks if all objects in a collection are scheduled to be stored. | |
* | |
* @param objects The collection of objects to check. | |
* @return {@code true} if all objects are scheduled to be stored, {@code false} otherwise. | |
*/ | |
public boolean willStoreAll(Collection<Object> objects) { | |
if(isRollbackOnly()) return false; | |
return read(()->valuesToStore.containsAll(objects)); | |
} | |
/** | |
* Attempts to commit changes to the underlying storage. Unlike {@link #commit()}, this method | |
* does not throw an exception if the transaction is marked as rollback-only or if there are no changes. | |
* It returns {@code false} in these cases, indicating that no commit occurred. | |
* | |
* @return {@code true} if the commit was successful, {@code false} otherwise. | |
*/ | |
public boolean attemptCommit() { | |
if(read(()->rollbackOnly) || isEmptyOrNull(valuesToStore)) { | |
return false; | |
} | |
if (isNotEmptyOrNull(valuesToStore)) { | |
write(() -> { | |
try { | |
persistenceStoring.storeAll(valuesToStore); | |
} finally { | |
valuesToStore.clear(); | |
} | |
}); | |
} | |
return true; | |
} | |
/** | |
* Commits the changes made within this context to the underlying storage. | |
* This method stores all pending objects and clears the list of objects to be stored. | |
* | |
* @throws IllegalStateException If the transaction is marked as rollback-only. | |
*/ | |
public void commit() { | |
if(read(()->rollbackOnly)) { | |
throw new IllegalStateException("Cannot commit changes - roll back only"); | |
} | |
if (isNotEmptyOrNull(valuesToStore)) { | |
write(() -> { | |
try { | |
persistenceStoring.storeAll(valuesToStore); | |
} finally { | |
valuesToStore.clear(); | |
} | |
}); | |
} | |
} | |
/** | |
* Marks the current transaction to be rolled back. Any subsequent attempt to commit | |
* will result in an {@link IllegalStateException}. | |
*/ | |
public void rollbackOnly() { | |
if(!rollbackOnly) | |
write(()->rollbackOnly=true); | |
} | |
/** | |
* Checks if the current transaction is marked for rollback. | |
* | |
* @return {@code true} if the transaction is marked for rollback, {@code false} otherwise. | |
*/ | |
public boolean isRollbackOnly(){ | |
return read(()->rollbackOnly); | |
} | |
/** | |
* Adds an object to the set of objects to be persisted. The object will be stored when | |
* {@link #commit()} is called. | |
* | |
* @param o The object to add. | |
*/ | |
public void addToPersist(Object o) { | |
if(isRollbackOnly()) return; | |
if (o != null) | |
write(() -> valuesToStore.add(o)); | |
} | |
/** | |
* Adds a collection of objects to the set of objects to be persisted. The objects will | |
* be stored when {@link #commit()} is called. | |
* | |
* @param objects The collection of objects to add. | |
*/ | |
public void addAllToPersist(Collection<Object> objects) { | |
if(isRollbackOnly()) return; | |
if (isNotEmptyOrNull(objects)) { | |
var nonNulls = objects.stream().filter(Objects::nonNull).toList(); | |
if (!nonNulls.isEmpty()) | |
write(() -> valuesToStore.addAll(nonNulls)); | |
} | |
} | |
/** | |
* Adds a variable number of objects to the set of objects to be persisted. The objects | |
* will be stored when {@link #commit()} is called. | |
* | |
* @param objects The objects to add. | |
*/ | |
public void addAllToPersist(Object... objects) { | |
if(isRollbackOnly()) return; | |
if (isNotEmptyOrNull(objects)) { | |
var nonNulls = Stream.of(objects).filter(Objects::nonNull).toArray(); | |
if (nonNulls.length > 0) | |
write(() -> Collections.addAll(valuesToStore, nonNulls)); | |
} | |
} | |
/** | |
* Stores a single object using the underlying persistence mechanism. | |
* @param instance the object to store. | |
* @return the persisted object id | |
*/ | |
@Override | |
public long store(Object instance) { | |
long id = persistenceStoring.store(instance); | |
LOGGER.log(DEBUG, () -> logMessages(STR."Stored (es#\{id})", instance)); | |
return id; | |
} | |
/** | |
* Stores all given instances. | |
* | |
* @param instances the instances to store | |
* @return array containing ids | |
*/ | |
@Override | |
public long[] storeAll(Object... instances) { | |
return persistenceStoring.storeAll(instances); | |
} | |
/** | |
* Stores all given instances. | |
* | |
* @param instances the instances to store | |
*/ | |
@Override | |
public void storeAll(Iterable<?> instances) { | |
persistenceStoring.storeAll(instances); | |
} | |
/** | |
* Logs messages for entity operations. | |
* | |
* @param prefix The prefix for the log message (e.g., "Created", "Updated"). | |
* @param identity The entity involved in the operation. | |
*/ | |
public static String logMessages(String prefix, Object identity) { | |
if(isEmptyOrNull(identity)) return ""; | |
var klass = getEntityName(identity); | |
var withId = identity instanceof WithId<?> i? STR." with id:\{i.id()} ":""; | |
var withName = identity instanceof WithName n? STR." with name:\{n.name()}":""; | |
return STR."\{prefix} \{klass} entity\{withId}\{withName}"; | |
} | |
private static String getEntityName(Object identity){ | |
return identity.getClass().getSimpleName(); | |
} | |
/** | |
* A dummy implementation of {@link PersistenceStoring} that performs no actual storage operations. | |
* This is used when a null {@link PersistenceStoring} is provided to the constructor. | |
*/ | |
public final static PersistenceStoring DUMMY_PERSISTENCE_STORING_INSTANCE = new PersistenceStoring() { | |
@Override | |
public long store(Object instance) { | |
LOGGER.log(INFO, () -> logMessages("Fake store(...) FAKE-stored ", instance)); | |
return 0; | |
} | |
@Override | |
public long[] storeAll(Object... instances) { | |
LOGGER.log(INFO, () -> "Fake storeAll(...) "); | |
return new long[instances.length]; | |
} | |
@Override | |
public void storeAll(Iterable<?> instances) { | |
LOGGER.log(INFO, () -> "Fake storeAll(...) "); | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment