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 data, even in versions
020 * of IIQ less than 8.4. This is useful for creating multi-version plugins.
021 */
022@SuppressWarnings("JavaReflectionMemberAccess")
023public class AccessHistory {
024
025    /**
026     * The Versioned singleton instance
027     */
028    private static AccessHistory INSTANCE;
029
030    /**
031     * A lock used to prevent more than one class from constructing an instance of
032     * this class at the same time.
033     */
034    private static final Lock lock = new ReentrantLock();
035
036    /**
037     * The logger
038     */
039    private static final Log log = LogFactory.getLog(AccessHistory.class);
040
041    /**
042     * Gets an instance of the utility class with version-specific implementation
043     * classes already configured.
044     *
045     * @return An instance of {@link AccessHistory}
046     * @throws GeneralException on failures
047     */
048    public static AccessHistory get() throws GeneralException {
049        if (INSTANCE == null) {
050            try {
051                lock.lockInterruptibly();
052                try {
053                    if (INSTANCE == null) {
054                        INSTANCE = new AccessHistory();
055                    }
056                } finally {
057                    lock.unlock();
058                }
059            } catch(Exception e) {
060                throw new GeneralException(e);
061            }
062        }
063        return INSTANCE;
064    }
065
066
067    /**
068     * The cached flag indicating that access history is available
069     */
070    private final AtomicReference<Boolean> accessHistoryEnabled;
071    /**
072     * The cached Method to get the context
073     */
074    private final AtomicReference<Method> cachedAccessHistoryContextGetMethod;
075    /**
076     * The cached Method to get the environment
077     */
078    private final AtomicReference<Method> cachedAccessHistoryEnvGetMethod;
079
080    private AccessHistory() {
081        accessHistoryEnabled = new AtomicReference<>();
082        cachedAccessHistoryEnvGetMethod = new AtomicReference<>();
083        cachedAccessHistoryContextGetMethod = new AtomicReference<>();
084    }
085
086
087    /**
088     * Returns the SailPointContext associated with Access History
089     * @return The access history context, or an empty optional if not available
090     */
091    public Optional<SailPointContext> getAccessHistoryContext() throws GeneralException {
092        String version = Version.getVersion();
093        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
094        if (is84 && this.isAccessHistoryEnabled()) {
095            try {
096                if (cachedAccessHistoryContextGetMethod.get() == null) {
097                    Class<?> ahu = Class.forName("sailpoint.accesshistory.AccessHistoryUtil");
098                    Method method = ahu.getMethod("getAccessHistoryContext");
099                    cachedAccessHistoryContextGetMethod.set(method);
100                }
101                SailPointContext ahContext = (SailPointContext) cachedAccessHistoryContextGetMethod.get().invoke(null);
102
103                return Optional.of(ahContext);
104            } catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
105                log.debug("Caught an exception constructing the 8.4 access history feature", e);
106            } catch(ClassNotFoundException e) {
107                throw new GeneralException("This environment appears to be 8.4, but AccessHistoryUtil was not found", e);
108            } catch (InvocationTargetException e) {
109                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
110            }
111        } else {
112            log.debug("Access history is not available and/or enabled (version = " + version + ")");
113        }
114
115        return Optional.empty();
116    }
117
118    /**
119     * Returns the Environment associated with Access History
120     * @return The access history environment, or an empty optional if not available
121     */
122    public Optional<Environment> getAccessHistoryEnvironment() throws GeneralException {
123        String version = Version.getVersion();
124        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
125        if (is84 && 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                log.debug("Caught an exception constructing the 8.4 access history feature", e);
136            } catch (InvocationTargetException e) {
137                throw new GeneralException("Exception thrown while constructing the AH Environment", e);
138            }
139        } else {
140            log.debug("Access history is not available and/or enabled (version = " + version + ")");
141        }
142
143        return Optional.empty();
144    }
145
146    /**
147     * Returns true if access history is enabled. The result will be cached
148     * so that reflection is not used for every call of this method.
149     *
150     * @return True, if this is 8.4 and access history is enabled
151     */
152    public boolean isAccessHistoryEnabled() {
153        if (accessHistoryEnabled.get() != null) {
154            return accessHistoryEnabled.get();
155        }
156        String version = Version.getVersion();
157        boolean is84 = Util.isNotNullOrEmpty(version) && (version.compareTo("8.4") >= 0);
158        if (is84) {
159            try {
160                boolean enabled = (boolean) Version.class.getMethod("isAccessHistoryEnabled").invoke(null);
161                accessHistoryEnabled.set(enabled);
162                return enabled;
163            } catch(Exception e) {
164                log.debug("Caught an exception checking whether access history is enabled", e);
165            }
166        }
167
168        accessHistoryEnabled.set(false);
169        return false;
170    }
171
172
173}