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}