001package com.identityworksllc.iiq.common;
002
003import sailpoint.server.Environment;
004
005import java.util.concurrent.atomic.AtomicReference;
006import java.util.function.Supplier;
007
008/**
009 * A container for a single value that is associated with the plugin cache
010 * version. If the plugin cache is updated, the version increments, and the
011 * provided {@link Supplier} will be invoked to get a new value on the next
012 * call to {@link #get()}.
013 *
014 * This allows consistent references to plugin-provided classes, even if the
015 * plugin is updated and a new version of the class is hot-deployed.
016 *
017 * Your {@link Supplier} code should always assume that the plugin cache has
018 * been refreshed since the last invocation and never cache plugin objects.
019 *
020 * The object type T, of course, cannot be the actual implementation
021 * in the plugin classloader, as that class will have become invalid when the
022 * plugin cache was reset. The type T should be either a JDK class or a custom
023 * interface implemented at the webapp layer.
024 *
025 * @param <T> the type of the value held in this reference
026 */
027public class VersionedReference<T> implements Supplier<T> {
028    /**
029     * The supplier that provides the initial value for this ThreadLocal.
030     */
031    private final Supplier<? extends T> supplier;
032
033    /**
034     * A container for the current value
035     */
036    private final AtomicReference<T> value;
037
038    /**
039     * The version of the plugin cache, used to determine if the threadlocal needs
040     * to be replaced.
041     */
042    private int version;
043
044    /**
045     * Creates a new VersionedThreadLocal with the given supplier. The initial
046     * value will not be calculated until the first call to {@link #get()}.
047     *
048     * @param supplier the initial value supplier
049     */
050    public VersionedReference(Supplier<? extends T> supplier) {
051        this.supplier = supplier;
052        this.value = new AtomicReference<>();
053        this.version = -1;
054    }
055
056    /**
057     * Returns the current value in this reference. If the plugin version has
058     * changed since the value was last set, the supplier will be called to get
059     * a new value.
060     *
061     * @return the current value in this ThreadLocal
062     * @see ThreadLocal#get()
063     */
064    @Override
065    public T get() {
066        int currentVersion = Environment.getEnvironment().getPluginsCache().getVersion();
067        if (currentVersion != version) {
068            synchronized (this) {
069                version = currentVersion;
070                value.set(supplier.get());
071            }
072        }
073        return value.get();
074    }
075
076}