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}