001package com.identityworksllc.iiq.common;
002
003import org.apache.commons.logging.Log;
004import org.apache.commons.logging.LogFactory;
005import sailpoint.Version;
006import sailpoint.api.SailPointContext;
007import sailpoint.server.Environment;
008import sailpoint.tools.GeneralException;
009import sailpoint.tools.Util;
010
011import java.lang.reflect.InvocationTargetException;
012import java.lang.reflect.Method;
013import java.util.Optional;
014import java.util.concurrent.atomic.AtomicReference;
015import java.util.concurrent.locks.Lock;
016import java.util.concurrent.locks.ReentrantLock;
017
018/**
019 * A singleton utility for retrieving AccessHistory-related objects, gracefully
020 * failing in pre-8.4 versions of IIQ.
021 *
022 * In versions of IIQ where Access History is not supported, the various
023 * methods will return empty {@link Optional} objects.
024 */
025public class AccessHistory {
026
027    /**
028     * The Versioned singleton instance
029     */
030    private static AccessHistory INSTANCE;
031
032    /**
033     * A lock used to prevent more than one class from constructing an instance of
034     * this class at the same time.
035     */
036    private static final Lock lock = new ReentrantLock();
037
038    /**
039     * The logger
040     */
041    private static final Log log = LogFactory.getLog(AccessHistory.class);
042    /**
043     * The cached flag indicating that access history is available
044     */
045    private final AtomicReference<Boolean> accessHistoryEnabled;
046    /**
047     * The cached Method to get the context
048     */
049    private final AtomicReference<Method> cachedAccessHistoryContextGetMethod;
050    /**
051     * The cached Method to get the environment
052     */
053    private final AtomicReference<Method> cachedAccessHistoryEnvGetMethod;
054
055    /**
056     * Private constructor
057     */
058    private AccessHistory() {
059        accessHistoryEnabled = new AtomicReference<>();
060        cachedAccessHistoryEnvGetMethod = new AtomicReference<>();
061        cachedAccessHistoryContextGetMethod = new AtomicReference<>();
062    }
063
064    /**
065     * Gets an instance of the utility class with version-specific implementation
066     * classes already configured.
067     *
068     * @return An instance of {@link AccessHistory}
069     * @throws GeneralException on failures
070     */
071    public static AccessHistory get() throws GeneralException {
072        if (INSTANCE == null) {
073            try {
074                lock.lockInterruptibly();
075                try {
076                    if (INSTANCE == null) {
077                        INSTANCE = new AccessHistory();
078                    }
079                } finally {
080                    lock.unlock();
081                }
082            } catch(Exception e) {
083                throw new GeneralException(e);
084            }
085        }
086        return INSTANCE;
087    }
088
089    /**
090     * Returns a newly created SailPointContext associated with Access History
091     * @return The access history context, or an empty optional if not available
092     * @throws GeneralException on failures
093     */
094    public Optional<SailPointContext> createAccessHistoryContext() throws GeneralException {
095        if (this.isAccessHistoryEnabled()) {
096            try {
097                Class<?> dbInstanceClass = Class.forName("sailpoint.api.DatabaseInstance");
098                Method method = dbInstanceClass.getMethod("valueOf", String.class);
099                Object dbInstance = method.invoke(null, "ACCESS_HISTORY");
100
101                Class<?> spFactory = Class.forName("sailpoint.api.SailPointFactory");
102                Method getContextMethod = spFactory.getMethod("createPrivateContext", dbInstanceClass);
103                SailPointContext ahContext = (SailPointContext) getContextMethod.invoke(null, dbInstance);
104
105                return Optional.of(ahContext);
106            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
107                log.debug("Caught an exception constructing the 8.4 access history feature", e);
108            } catch(ClassNotFoundException e) {
109                log.warn("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
110                throw new GeneralException("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
111            } catch (InvocationTargetException e) {
112                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
113            }
114        } else {
115            log.debug("Access history is not available or is not enabled (version = " + Version.getVersion() + ")");
116        }
117
118        return Optional.empty();
119    }
120
121    /**
122     * Returns the SailPointContext associated with Access History
123     * @return The access history context, or an empty optional if not available
124     */
125    public Optional<SailPointContext> getAccessHistoryContext() throws GeneralException {
126        if (this.isAccessHistoryEnabled()) {
127            try {
128                if (cachedAccessHistoryContextGetMethod.get() == null) {
129                    Class<?> ahu = Class.forName("sailpoint.accesshistory.AccessHistoryUtil");
130                    Method method = ahu.getMethod("getAccessHistoryContext");
131                    cachedAccessHistoryContextGetMethod.set(method);
132                }
133                SailPointContext ahContext = (SailPointContext) cachedAccessHistoryContextGetMethod.get().invoke(null);
134
135                return Optional.of(ahContext);
136            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
137                log.debug("Caught an exception constructing the 8.4 access history feature", e);
138            } catch(ClassNotFoundException e) {
139                log.warn("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
140                throw new GeneralException("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
141            } catch (InvocationTargetException e) {
142                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
143            }
144        } else {
145            log.debug("Access history is not available or is not enabled (version = " + Version.getVersion() + ")");
146        }
147
148        return Optional.empty();
149    }
150
151    /**
152     * Returns the Environment associated with Access History
153     * @return The access history environment, or an empty optional if not available
154     */
155    public Optional<Environment> getAccessHistoryEnvironment() throws GeneralException {
156        if (this.isAccessHistoryEnabled()) {
157            try {
158                if (cachedAccessHistoryEnvGetMethod.get() == null) {
159                    Method method = Environment.class.getMethod("getEnvironmentAccessHistory");
160                    cachedAccessHistoryEnvGetMethod.set(method);
161                }
162                Environment ahEnvironment = (Environment) cachedAccessHistoryEnvGetMethod.get().invoke(null);
163
164                return Optional.of(ahEnvironment);
165            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
166                this.accessHistoryEnabled.set(false);
167                log.warn("Caught an exception accessing the 8.4 access history feature", e);
168            } catch (InvocationTargetException e) {
169                this.accessHistoryEnabled.set(false);
170                log.warn("Caught an exception accessing the 8.4 access history feature", e);
171                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
172            }
173        } else {
174            log.debug("Access history is not available and/or enabled (version = " + Version.getVersion() + ")");
175        }
176
177        return Optional.empty();
178    }
179
180    /**
181     * Returns true if access history is available and enabled. The result will be cached
182     * so that reflection is not used for every call of this method.
183     *
184     * @return True, if this is 8.4 or higher, and access history is enabled
185     */
186    public boolean isAccessHistoryEnabled() {
187        if (accessHistoryEnabled.get() != null) {
188            return accessHistoryEnabled.get();
189        }
190        boolean is84 = Utilities.isIIQVersionAtLeast(CommonConstants.VERSION_8_4);
191        if (is84) {
192            try {
193                boolean enabled = (boolean) Version.class.getMethod("isAccessHistoryEnabled").invoke(null);
194                accessHistoryEnabled.set(enabled);
195                return enabled;
196            } catch(Exception e) {
197                log.debug("Caught an exception checking whether access history is enabled", e);
198            }
199        }
200
201        accessHistoryEnabled.set(false);
202        return false;
203    }
204
205    /**
206     * Releases the given Access History context
207     * @param ahContext the context to release
208     * @throws GeneralException on failures
209     */
210    public void releaseAccessHistoryContext(SailPointContext ahContext) throws GeneralException {
211        if (this.isAccessHistoryEnabled() && ahContext != null) {
212            try {
213                Class<?> ahu = Class.forName("sailpoint.accesshistory.AccessHistoryUtil");
214                Method method = ahu.getMethod("safeRelease", SailPointContext.class);
215
216                method.invoke(null, ahContext);
217            } catch(Exception e) {
218                throw new GeneralException(e);
219            }
220        }
221    }
222
223
224}