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

io.github.mike10004.containment.lifecycle.LifecyclingCachingProvider Maven / Gradle / Ivy

The newest version!
package io.github.mike10004.containment.lifecycle;

import javax.annotation.Nullable;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static java.util.Objects.requireNonNull;

/**
 * Implementation of a provider of a resource that has a lifecycle.
 * @param  provided resource type
 */
public class LifecyclingCachingProvider implements CachingProvider {

    private final Lifecycle lifecycle;
    private final ConcurrentCache concurrentCache;
    private final Consumer eventListener;
    private final AtomicBoolean finishInvoked;

    /**
     * Constructs an instance.
     * @param lifecycle the lifecycle
     */
    public LifecyclingCachingProvider(Lifecycle lifecycle) {
        this(lifecycle, LifecycleEvent.inactiveConsumer());
    }

    /**
     * Constructs an instance.
     * @param lifecycle lifecycle
     * @param eventListener event listener
     */
    public LifecyclingCachingProvider(Lifecycle lifecycle, Consumer eventListener) {
        this.lifecycle = requireNonNull(lifecycle);
        concurrentCache = new ConcurrentCache();
        this.eventListener = requireNonNull(eventListener);
        finishInvoked = new AtomicBoolean(false);
    }

    private static class LifecycleFinishedException extends RuntimeException {}

    /**
     * Returns a provision, after computing or recalling the cached computation result.
     * @return the provision
     */
    @Override
    public final Provision provide() {
        boolean alreadyInvoked = finishInvoked.get();
        if (alreadyInvoked) {
            return Provision.failed(new LifecycleFinishedException());
        }
        notify(LifecycleEvent.Category.PROVIDE_STARTED);
        AtomicBoolean computed = new AtomicBoolean(false);
        Provision invocation = concurrentCache.compute(new Supplier>(){
            @Override
            public Provision get() {
                computed.set(true);
                return computeOnce();
            }
        });
        notify(LifecycleEvent.Category.PROVIDE_COMPLETED, String.format("%s %s", computed.get() ? "computed" : "recalled", invocation));
        return invocation;
    }

    protected D doCommission() throws Exception {
        notify(LifecycleEvent.Category.COMMISSION_STARTED);
        D commissioned = lifecycle.commission();
        Verify.verifyNotNull(commissioned, "lifecycle produced non-null commission() result");
        return commissioned;
    }

    protected Provision computeOnce() {
        try {
            D val = doCommission();
            notify(LifecycleEvent.Category.COMMISSION_SUCCEEDED);
            return Computation.succeeded(val);
        } catch (Throwable t) {
            notify(LifecycleEvent.Category.COMMISSION_FAILED);
            return Computation.failed(t);
        }
    }

    /**
     * Finishes the lifecycle of the cached object.
     */
    public void finishLifecycle() {
        boolean firstInvocation = finishInvoked.compareAndSet(false, true);
        if (!firstInvocation) {
            return;
        }
        notify(LifecycleEvent.of(LifecycleEvent.Category.FINISH_STARTED));
        try {
            lifecycle.decommission();
        } catch (RuntimeException t) {
            handleTearDownError(t);
        }
        notify(LifecycleEvent.of(LifecycleEvent.Category.FINISH_COMPLETED));
    }

    protected void handleTearDownError(RuntimeException t) {
        throw t;
    }

    private class ConcurrentCache {

        private final ConcurrentMap> concurrencyManager = new ConcurrentHashMap<>(1);
        private transient final Object computeKey = new Object();

        public Provision compute(Supplier> computer) {
            return concurrencyManager.computeIfAbsent(computeKey, k -> computer.get());
        }

        @Override
        public String toString() {
            return String.format("ConcurrentCache[size=%d]", concurrencyManager.size());
        }

        @Nullable
        public Provision getIfPresent() {
            return concurrencyManager.get(computeKey);
        }
    }

    @Nullable
    protected Provision getProvisionIfAvailable() {
        @Nullable Provision provision = concurrentCache.getIfPresent();
        return provision;
    }

    protected void notify(LifecycleEvent.Category category, String message) {
        notify(new LifecycleEvent(category, message));
    }

    protected void notify(LifecycleEvent event) {
        eventListener.accept(event);
    }

    protected void notify(LifecycleEvent.Category category) {
        eventListener.accept(LifecycleEvent.of(category));
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", LifecyclingCachingProvider.class.getSimpleName() + "[", "]")
                .add("lifecycle=" + lifecycle)
                .add("concurrentCache=" + concurrentCache)
                .add("eventListener=" + eventListener)
                .toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy