All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.binjr.common.concurrent.CloseableResourceManager Maven / Gradle / Ivy

There is a newer version: 3.20.1
Show newest version
package eu.binjr.common.concurrent;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Closeable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;

/**
 * A generic resource manager for safely initializing, sharing and disposing
 * singletons amongst multiple consumer objects, on multiple threads.
 * 

* The resource manager will guarantee that only one instance for each supplied * key is ever is initialized and returned when required. *

*

* Reference counting is used to ensure that the actual closing of a resource * only happens once all registered consumers have released it. *

* * @param Resource type * @author Frederic Thevenet */ public class CloseableResourceManager { private static final Log logger = LogFactory.getLog(CloseableResourceManager.class); private final Map> resources = new HashMap<>(); private final ReadWriteLockHelper managerLock = new ReadWriteLockHelper(new ReentrantReadWriteLock()); /** * Resource holder Resource type * * @param Resource type */ private final class ResourceHolder { private AtomicInteger referenceCount = new AtomicInteger(0); private volatile boolean closed; private U instance; /** * Constructor * * @param instance Resource instance */ private ResourceHolder(U instance) { this.instance = instance; } /** * Get Resource instance * * @return Resource instance */ private U getInstance() { return instance; } /** * Register resource * * @return Reference count */ private int register() { return referenceCount.incrementAndGet(); } /** * Release resource * * @return Reference count * @throws Exception exception */ private int release() throws Exception { int refLeft = referenceCount.decrementAndGet(); if (refLeft < 1) { if (!closed) { try { instance.close(); } finally { closed = true; } } } return refLeft; } } /** * Registers a consumer with a resource, identified by the supplied key. *

* This method must only be called once per consumer (or the release methods * must be called accordingly), otherwise the resource may never be * released. *

* * @param key the key to identify the resource to register. * @param resourceFactory a factory method used to build a new instance of the resource * if one doesn't already exist. * @return the registered resource */ public T acquire(String key, Supplier resourceFactory) { return managerLock.write().lock(() -> { var r = resources.computeIfAbsent(key, k -> new ResourceHolder<>(resourceFactory.get())); r.register(); return r.getInstance(); }); } /** * Registers a consumer with a resource, identified by the supplied key. *

* This method must only be called once per consumer (or the release methods * must be called accordingly), otherwise the resource may never be * released. *

* * @param key the key to identify the resource to register. * @param resource the resource to register * @return Reference count */ public int register(String key, T resource) { return managerLock.write().lock(() -> { var r = resources.computeIfAbsent(key, k -> new ResourceHolder<>(resource)); return r.register(); }); } /** * Releases the resource identified by the supplied key. *

* If all references to this resource are released, the resource is closed. *

*

* Warning: Registering a closed resources with the same key will * cause a new instance to be created. *

* * @param key the key to identify the resource to release. * @return the number of references left for the specified resource. * @throws Exception if an error occurs while closing the resource. */ public int release(String key) throws Exception { return managerLock.write().lock(() -> { ResourceHolder r = resources.get(key); if (r != null) { try { return r.release(); } finally { if (r.closed) { resources.remove(key); } } } else { logger.warn("Trying to release a resource that is not registered."); return -1; } }); } /** * Forces the release and subsequent closing of the specified resource, * regardless of how many references to it are still held by other * consumers. *

* Use with caution. *

* * @param key the key to identify the resource to close. * @throws Exception if an error occurs while closing the resource. */ public void forceReleaseAndClose(String key) throws Exception { managerLock.write().lock(() -> { ResourceHolder r = resources.get(key); if (r != null) { while (r.release() > 0) { // Nothing } } else { logger.warn("Trying to close a resource that is not registered."); } }); } /** * Returns the resource identified by the supplied key. * * @param key the key to identify the resource to release. * @return An {@link Optional} wrapping the resource identified by the supplied key. */ public Optional get(String key) { return managerLock.read().lock(() -> { ResourceHolder r = resources.get(key); if (r != null) { return Optional.of(r.getInstance()); } return Optional.empty(); }); } /** * Returns true if a resource identified by the provided key is registered, * false otherwise. * * @param key the key to identify the resource. * @return true if a resource identified by the provided key is registered, * false otherwise. */ public boolean isRegistered(String key) { return managerLock.read().lock(() -> resources.containsKey(key)); } }