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}