001package com.identityworksllc.iiq.common;
002
003import sailpoint.api.SailPointContext;
004import sailpoint.api.SailPointFactory;
005import sailpoint.tools.GeneralException;
006
007import java.util.Objects;
008import java.util.concurrent.atomic.AtomicReference;
009import java.util.concurrent.locks.Lock;
010import java.util.concurrent.locks.ReentrantLock;
011import java.util.function.Supplier;
012
013/**
014 * Lazily initializes an instance of T in a thread-safe manner on the
015 * first call to {@link #get()}. The instance of T returned from the initializer
016 * must not be associated with any particular session.
017 *
018 * If an associator is defined, the object will be associated with the current session
019 * via a call to {@link Associator#associate(SailPointContext, Object)} each time {@link #get()}
020 * is called.
021 *
022 * @param <T> The type to lazily initialize
023 */
024public final class Lazy<T> implements Supplier<Maybe<T>> {
025    /**
026     * Associates the instance of T with the given SailPointContext.
027     * @param <T> The type to associate
028     */
029    @FunctionalInterface
030    public interface Associator<T> {
031        /**
032         * Associates the given instance of T with the provided SailPointContext.
033         * @param context The SailPointContext to associate with
034         * @param instance The instance of T to associate
035         * @throws GeneralException if association fails
036         * @return The associated instance of T
037         */
038        T associate(SailPointContext context, T instance) throws GeneralException;
039    }
040
041    /**
042     * Initializer functional interface for creating instances of T.
043     * @param <T> The type to initialize
044     */
045    @FunctionalInterface
046    public interface Initializer<T> {
047        /**
048         * Initializes and returns an instance of T.
049         * @return The initialized instance of T
050         * @throws GeneralException if initialization fails
051         */
052        T initialize() throws GeneralException;
053    }
054
055    /**
056     * The associator used to associate instances of T with a SailPointContext.
057     */
058    private final Associator<T> associator;
059
060    /**
061     * The initializer used to create the instance of T
062     */
063    private final Initializer<T> initializer;
064
065    /**
066     * The lazily initialized instance of T, wrapped in an AtomicReference to allow
067     * 'null' to be a valid value.
068     */
069    private AtomicReference<T> instance;
070
071    /**
072     * Lock to ensure thread-safe initialization
073     */
074    private final Lock lock;
075
076    /**
077     * Constructs a Lazy instance with the given initializer.
078     * @param initializer The initializer to create instances of T
079     */
080    public Lazy(Initializer<T> initializer) {
081        this(initializer, null);
082    }
083
084    /**
085     * Constructs a Lazy instance with the given initializer and associator.
086     * @param initializer The initializer to create instances of T
087     * @param associator The associator to associate instances of T with a SailPointContext
088     */
089    public Lazy(Initializer<T> initializer, Associator<T> associator) {
090        this.initializer = Objects.requireNonNull(initializer, "Initializer cannot be null");
091        this.associator = associator;
092        this.lock = new ReentrantLock();
093    }
094
095    /**
096     * Returns the lazily initialized instance of T, initializing it if necessary.
097     * If an associator is defined, the object will also be associated with the
098     * SP context for the current thread.
099     *
100     * If you use an associator, the instance of T returned from this method may
101     * be different each time, as it is associated with the current context.
102     *
103     * @return The instance of T wrapped in a {@link Maybe}
104     */
105    public Maybe<T> get() {
106        try {
107            if (instance == null) {
108                try {
109                    lock.lockInterruptibly();
110                    try {
111                        if (instance == null) {
112                            instance = new AtomicReference<>(initializer.initialize());
113                        }
114                    } finally {
115                        lock.unlock();
116                    }
117                } catch (InterruptedException e) {
118                    Thread.currentThread().interrupt();
119                    throw new GeneralException("Thread was interrupted during lazy initialization", e);
120                }
121            }
122            T object = instance.get();
123            if (associator != null) {
124                SailPointContext currentContext = SailPointFactory.getCurrentContext();
125                if (currentContext != null) {
126                    object = associator.associate(currentContext, object);
127                }
128            }
129            return Maybe.of(object);
130        } catch(GeneralException e) {
131            return Maybe.of(e);
132        }
133    }
134}