Skip to content

Instantly share code, notes, and snippets.

@hrstoyanov
Last active February 19, 2025 03:41
Show Gist options
  • Save hrstoyanov/55c57c824cf3c46a90be497d39df9240 to your computer and use it in GitHub Desktop.
Save hrstoyanov/55c57c824cf3c46a90be497d39df9240 to your computer and use it in GitHub Desktop.
/**
* 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