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    /**
044     * Gets an instance of the utility class with version-specific implementation
045     * classes already configured.
046     *
047     * @return An instance of {@link AccessHistory}
048     * @throws GeneralException on failures
049     */
050    public static AccessHistory get() throws GeneralException {
051        if (INSTANCE == null) {
052            try {
053                lock.lockInterruptibly();
054                try {
055                    if (INSTANCE == null) {
056                        INSTANCE = new AccessHistory();
057                    }
058                } finally {
059                    lock.unlock();
060                }
061            } catch(Exception e) {
062                throw new GeneralException(e);
063            }
064        }
065        return INSTANCE;
066    }
067
068
069    /**
070     * The cached flag indicating that access history is available
071     */
072    private final AtomicReference<Boolean> accessHistoryEnabled;
073    /**
074     * The cached Method to get the context
075     */
076    private final AtomicReference<Method> cachedAccessHistoryContextGetMethod;
077    /**
078     * The cached Method to get the environment
079     */
080    private final AtomicReference<Method> cachedAccessHistoryEnvGetMethod;
081
082    private AccessHistory() {
083        accessHistoryEnabled = new AtomicReference<>();
084        cachedAccessHistoryEnvGetMethod = new AtomicReference<>();
085        cachedAccessHistoryContextGetMethod = new AtomicReference<>();
086    }
087
088
089    /**
090     * Returns the SailPointContext associated with Access History
091     * @return The access history context, or an empty optional if not available
092     */
093    public Optional<SailPointContext> getAccessHistoryContext() throws GeneralException {
094        boolean is84 = Utilities.isIIQVersionAtLeast(CommonConstants.VERSION_8_4);
095        if (this.isAccessHistoryEnabled()) {
096            try {
097                if (cachedAccessHistoryContextGetMethod.get() == null) {
098                    Class<?> ahu = Class.forName("sailpoint.accesshistory.AccessHistoryUtil");
099                    Method method = ahu.getMethod("getAccessHistoryContext");
100                    cachedAccessHistoryContextGetMethod.set(method);
101                }
102                SailPointContext ahContext = (SailPointContext) cachedAccessHistoryContextGetMethod.get().invoke(null);
103
104                return Optional.of(ahContext);
105            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
106                log.debug("Caught an exception constructing the 8.4 access history feature", e);
107            } catch(ClassNotFoundException e) {
108                log.warn("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
109                throw new GeneralException("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
110            } catch (InvocationTargetException e) {
111                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
112            }
113        } else {
114            log.debug("Access history is not available or is not enabled (version = " + Version.getVersion() + ")");
115        }
116
117        return Optional.empty();
118    }
119
120    /**
121     * Returns the Environment associated with Access History
122     * @return The access history environment, or an empty optional if not available
123     */
124    public Optional<Environment> getAccessHistoryEnvironment() throws GeneralException {
125        if (this.isAccessHistoryEnabled()) {
126            try {
127                if (cachedAccessHistoryEnvGetMethod.get() == null) {
128                    Method method = Environment.class.getMethod("getEnvironmentAccessHistory");
129                    cachedAccessHistoryEnvGetMethod.set(method);
130                }
131                Environment ahEnvironment = (Environment) cachedAccessHistoryEnvGetMethod.get().invoke(null);
132
133                return Optional.of(ahEnvironment);
134            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
135                this.accessHistoryEnabled.set(false);
136                log.warn("Caught an exception accessing the 8.4 access history feature", e);
137            } catch (InvocationTargetException e) {
138                this.accessHistoryEnabled.set(false);
139                log.warn("Caught an exception accessing the 8.4 access history feature", e);
140                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
141            }
142        } else {
143            log.debug("Access history is not available and/or enabled (version = " + Version.getVersion() + ")");
144        }
145
146        return Optional.empty();
147    }
148
149    /**
150     * Returns true if access history is available and enabled. The result will be cached
151     * so that reflection is not used for every call of this method.
152     *
153     * @return True, if this is 8.4 or higher, and access history is enabled
154     */
155    public boolean isAccessHistoryEnabled() {
156        if (accessHistoryEnabled.get() != null) {
157            return accessHistoryEnabled.get();
158        }
159        boolean is84 = Utilities.isIIQVersionAtLeast(CommonConstants.VERSION_8_4);
160        if (is84) {
161            try {
162                boolean enabled = (boolean) Version.class.getMethod("isAccessHistoryEnabled").invoke(null);
163                accessHistoryEnabled.set(enabled);
164                return enabled;
165            } catch(Exception e) {
166                log.debug("Caught an exception checking whether access history is enabled", e);
167            }
168        }
169
170        accessHistoryEnabled.set(false);
171        return false;
172    }
173
174
175}