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        String version = Version.getVersion();
095        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
096        if (is84 && this.isAccessHistoryEnabled()) {
097            try {
098                if (cachedAccessHistoryContextGetMethod.get() == null) {
099                    Class<?> ahu = Class.forName("sailpoint.accesshistory.AccessHistoryUtil");
100                    Method method = ahu.getMethod("getAccessHistoryContext");
101                    cachedAccessHistoryContextGetMethod.set(method);
102                }
103                SailPointContext ahContext = (SailPointContext) cachedAccessHistoryContextGetMethod.get().invoke(null);
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                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 and/or enabled (version = " + version + ")");
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        String version = Version.getVersion();
126        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
127        if (is84 && this.isAccessHistoryEnabled()) {
128            try {
129                if (cachedAccessHistoryEnvGetMethod.get() == null) {
130                    Method method = Environment.class.getMethod("getEnvironmentAccessHistory");
131                    cachedAccessHistoryEnvGetMethod.set(method);
132                }
133                Environment ahEnvironment = (Environment) cachedAccessHistoryEnvGetMethod.get().invoke(null);
134
135                return Optional.of(ahEnvironment);
136            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
137                log.debug("Caught an exception constructing the 8.4 access history feature", e);
138            } catch (InvocationTargetException e) {
139                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
140            }
141        } else {
142            log.debug("Access history is not available and/or enabled (version = " + version + ")");
143        }
144
145        return Optional.empty();
146    }
147
148    /**
149     * Returns true if access history is enabled. The result will be cached
150     * so that reflection is not used for every call of this method.
151     *
152     * @return True, if this is 8.4 and access history is enabled
153     */
154    public boolean isAccessHistoryEnabled() {
155        if (accessHistoryEnabled.get() != null) {
156            return accessHistoryEnabled.get();
157        }
158        String version = Version.getVersion();
159        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
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}