package it.cavallium.dbengine.database.remote; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.jetbrains.annotations.Nullable; import reactor.core.publisher.Mono; /** * * @param Identifier type * @param Value type */ public class ReferencedResources { private final ResourceGetter resourceGetter; private final ResourceFreer resourceFree; private final AtomicLong nextReferenceId = new AtomicLong(1); private final ConcurrentHashMap identifierToReferenceId = new ConcurrentHashMap<>(); private final ConcurrentHashMap identifierByReferenceId = new ConcurrentHashMap<>(); private final ConcurrentHashMap resourceByReferenceId = new ConcurrentHashMap<>(); public ReferencedResources(ResourceGetter resourceGetter, ResourceFreer resourceFree) { this.resourceGetter = resourceGetter; this.resourceFree = resourceFree; } protected Mono obtain(I identifier, E extraParams) { return resourceGetter.obtain(identifier, extraParams); } protected Mono free(V resource) { return resourceFree.free(resource); } public Mono getReference(I identifier, @Nullable E extraParams) { Mono existingDbMono = Mono.fromSupplier(() -> identifierToReferenceId.get(identifier)); if (extraParams != null) { // Defer to avoid building this chain when not needed Mono dbCreationMono = Mono.defer(() -> this .obtain(identifier, extraParams) .flatMap(db -> Mono.fromCallable(() -> { long referenceId = nextReferenceId.getAndIncrement(); resourceByReferenceId.put(referenceId, db); identifierToReferenceId.put(identifier, referenceId); identifierByReferenceId.put(referenceId, identifier); return referenceId; }))); return existingDbMono.switchIfEmpty(dbCreationMono); } else { return existingDbMono .switchIfEmpty(Mono.error(() -> new NoSuchElementException("Resource not found: " + identifier))); } } public Mono> getResource(I identifier, @Nullable E extraParams) { Mono> existingDbMono = Mono.fromSupplier(() -> { var referenceId = identifierToReferenceId.get(identifier); if (referenceId == null) { return null; } var resource = resourceByReferenceId.get(referenceId); if (resource == null) { return null; } return new ReferencedResource<>(referenceId, resource); }); if (extraParams != null) { // Defer to avoid building this chain when not needed Mono> dbCreationMono = Mono.defer(() -> this .obtain(identifier, extraParams) .map(resource -> { long referenceId = nextReferenceId.getAndIncrement(); resourceByReferenceId.put(referenceId, resource); identifierToReferenceId.put(identifier, referenceId); identifierByReferenceId.put(referenceId, identifier); return new ReferencedResource<>(referenceId, resource); })); return existingDbMono.switchIfEmpty(dbCreationMono); } else { return existingDbMono .switchIfEmpty(Mono.error(() -> new NoSuchElementException("Resource not found: " + identifier))); } } public Mono getResource(long referenceId) { Mono existingDbMono = Mono.fromSupplier(() -> resourceByReferenceId.get(referenceId)); return existingDbMono.switchIfEmpty(Mono.error(() -> new NoSuchElementException("Resource not found: " + referenceId))); } public Mono releaseResource(I identifier) { return Mono.fromSupplier(() -> { var referenceId = identifierToReferenceId.remove(identifier); if (referenceId == null) { return null; } identifierByReferenceId.remove(referenceId); return resourceByReferenceId.remove(referenceId); }).flatMap(this::free); } public Mono releaseResource(long referenceId) { return Mono.fromSupplier(() -> { var identifier = identifierByReferenceId.remove(referenceId); if (identifier == null) { return null; } identifierToReferenceId.remove(identifier); return resourceByReferenceId.remove(referenceId); }).flatMap(this::free); } }