001package com.identityworksllc.iiq.common; 002 003import sailpoint.tools.Util; 004 005import java.util.ArrayList; 006import java.util.List; 007import java.util.concurrent.atomic.AtomicReference; 008 009import static com.identityworksllc.iiq.common.IIQFeature.*; 010 011/** 012 * A utility class for determining the current version of IIQ and what features 013 * are supported in that version. This class should be kept up to date with new 014 * releases and features in IIQ. 015 */ 016public final class IIQVersion { 017 /** 018 * The supported versions of IIQ, along with the features they introduce and 019 * the minimum Java version they require. Each version points to its previous 020 * version, forming a chain back to the first supported version (7.3). 021 * 022 * A version supports a feature if it or any previous version in the chain 023 * supports and does not remove the feature. 024 */ 025 public enum Version { 026 /** 027 * IIQ 7.3 028 */ 029 V7_3(null, 8, 7.3, IQServiceTLS), 030 031 /** 032 * IIQ 8.0 033 */ 034 V8_0(V7_3, 8, 8.0, AcceleratorPack), 035 /** 036 * IIQ 8.1 037 */ 038 V8_1(V8_0, 8, 8.1), 039 /** 040 * IIQ 8.1p1 041 */ 042 V8_1_P1(V8_1, 8, 8.1), 043 /** 044 * IIQ 8.1p2 045 */ 046 V8_1_P2(V8_1_P1, 8, 8.1), 047 /** 048 * IIQ 8.1p3 049 */ 050 V8_1_P3(V8_1_P2, 8, 8.1, EncapsulatedConnectors), 051 /** 052 * IIQ 8.1p4 053 */ 054 V8_1_P4(V8_1_P3, 8, 8.1), 055 /** 056 * IIQ 8.2 057 */ 058 V8_2(V8_1, 8, 8.2, FixedJDBCGetObject, EncapsulatedConnectors, RapidSetup, AIRecommender, HostSpecificRequestHandlers, AttributeSyncWorkflows, PluginOAuth2), 059 /** 060 * IIQ 8.2p1 061 */ 062 V8_2_P1(V8_2, 8, 8.2), 063 /** 064 * IIQ 8.2p2 065 */ 066 V8_2_P2(V8_2_P1, 8, 8.2), 067 /** 068 * IIQ 8.2p3 069 */ 070 V8_2_P3(V8_2_P2, 8, 8.2), 071 /** 072 * IIQ 8.2p4 073 */ 074 V8_2_P4(V8_2_P3, 8, 8.2), 075 /** 076 * IIQ 8.2p5 077 */ 078 V8_2_P5(V8_2_P4, 8, 8.2), 079 /** 080 * IIQ 8.2p6 081 */ 082 V8_2_P6(V8_2_P5, 8, 8.2), 083 /** 084 * IIQ 8.2p7 085 */ 086 V8_2_P7(V8_2_P6, 8, 8.2), 087 /** 088 * IIQ 8.3 089 */ 090 V8_3(V8_2, 8, 8.3, BundleProfileService), 091 /** 092 * IIQ 8.3p1 093 */ 094 V8_3_P1(V8_3, 8, 8.3), 095 /** 096 * IIQ 8.3p2 097 */ 098 V8_3_P2(V8_3_P1, 8, 8.3), 099 /** 100 * IIQ 8.3p3 101 */ 102 V8_3_P3(V8_3_P2, 8, 8.3), 103 /** 104 * IIQ 8.3p4 105 */ 106 V8_3_P4(V8_3_P3, 8, 8.3), 107 /** 108 * IIQ 8.3p5 109 */ 110 V8_3_P5(V8_3_P4, 8, 8.3, IQServiceTLSMandatory), 111 /** 112 * IIQ 8.4 113 */ 114 V8_4(V8_3, 11, 8.4, AccessHistory, DataExtract, not(AcceleratorPack)), 115 /** 116 * IIQ 8.4p1 117 */ 118 V8_4_P1(V8_4, 11, 8.4), 119 /** 120 * IIQ 8.4p2 121 */ 122 V8_4_P2(V8_4_P1, 11, 8.4), 123 /** 124 * IIQ 8.4p3 125 */ 126 V8_4_P3(V8_4_P2, 11, 8.4, IQServiceTLSMandatory), 127 /** 128 * IIQ 8.5 129 */ 130 V8_5(V8_4, 11, 8.5, IdentityAttributeAccessControls), 131 /** 132 * IIQ 8.5p1 133 */ 134 V8_5_P1(V8_5, 11, 8.5), 135 /** 136 * The latest known version of IIQ. This will be updated with each new release. 137 */ 138 Latest(V8_5_P1, 11, 8.5); 139 140 /** 141 * The list of features introduced (or removed) in this version 142 */ 143 private final List<FeatureWrapper> features = new ArrayList<>(); 144 145 /** 146 * The minimum Java version required for this version of IIQ 147 */ 148 private final int javaVersion; 149 150 /** 151 * The major version number (e.g., 8.3) 152 */ 153 private final double majorVersion; 154 155 /** 156 * The previous version in the chain, or null if this is the first version (7.3) 157 */ 158 private final Version previous; 159 160 /** 161 * Creates a new version enum 162 * @param previous the previous version in the chain, or null if this is the first version 163 * @param javaVersion the minimum Java version required for this version of IIQ 164 * @param majorVersion the major version number (e.g., 8.3) 165 * @param introducedFeatures the features introduced (or removed, if wrapped with not()) in this version 166 */ 167 Version(Version previous, int javaVersion, double majorVersion, Object... introducedFeatures) { 168 this.previous = previous; 169 this.javaVersion = javaVersion; 170 this.majorVersion = majorVersion; 171 172 if (introducedFeatures != null) { 173 for(Object obj : introducedFeatures) { 174 if (obj instanceof IIQFeature) { 175 features.add(new FeatureWrapper((IIQFeature) obj, false)); 176 } else if (obj instanceof FeatureWrapper) { 177 features.add((FeatureWrapper) obj); 178 } else { 179 throw new IllegalArgumentException("Invalid feature type: " + obj.getClass().getName()); 180 } 181 } 182 } 183 } 184 185 /** 186 * Returns true if this version is at least the specified other version. If the other 187 * version is a patch version, it will be rewound back to its base version for comparison. 188 * 189 * So for example, V8_1_P3.atLeast(V8_1_P1) will return true, as will V8_1_P3.atLeast(V8_1). 190 * 191 * Null indicates the earliest possible version, so this will always return true for null input. 192 * 193 * @param otherVersion the other version to compare against 194 * @return true if this version is at least the other version, false otherwise 195 */ 196 public boolean atLeast(Version otherVersion) { 197 if (otherVersion == null) { 198 return true; 199 } 200 201 if (otherVersion == this) { 202 return true; 203 } 204 205 Version baseOther = otherVersion.getBaseVersion(); 206 if (baseOther == this && otherVersion != this) { 207 // Same base but other is a patch and this is not, so this is not at least the other 208 return false; 209 } 210 211 Version current = this; 212 while (current != null) { 213 if (current == baseOther) { 214 return true; 215 } 216 current = current.previous; 217 } 218 219 return false; 220 } 221 222 /** 223 * Gets the base version (e.g., V8_1 for V8_1_P3) 224 * @return The base version 225 */ 226 public Version getBaseVersion() { 227 Version base = this; 228 while (base.previous != null && base.name().contains("_P")) { 229 base = base.previous; 230 } 231 return base; 232 } 233 234 /** 235 * Gets the major version of IIQ as a number 236 * @return The major version (e.g., 8.3) 237 */ 238 public double getMajorVersion() { 239 return majorVersion; 240 } 241 242 /** 243 * Gets the previous version in the chain, or null if this is the first version 244 * @return The previous version, or null 245 */ 246 public Version getPrevious() { 247 return previous; 248 } 249 250 /** 251 * Returns true if this version of IIQ supports the given feature. This version 252 * and all parent versions will be checked. 253 * 254 * @param feature The feature to check 255 * @return True if supported, false otherwise 256 */ 257 public boolean supportsFeature(IIQFeature feature) { 258 for(FeatureWrapper fw : features) { 259 if (fw.feature == feature) { 260 return !fw.negative; 261 } 262 } 263 if (previous != null) { 264 return previous.supportsFeature(feature); 265 } 266 return false; 267 } 268 269 /** 270 * Returns true if this version of IIQ requires at least the given Java version. 271 * For 8.3, for example, this will return true for 11 and 8. For 8.4, it will return 272 * false for 8 and true for 11. 273 * 274 * @param version The Java version to check 275 * @return True if supported, false otherwise 276 */ 277 public boolean supportsJavaVersion(int version) { 278 return javaVersion <= version; 279 } 280 } 281 282 /** 283 * A wrapper for a feature that indicates whether it is being added or removed in this version. 284 * This is for internal use only. 285 */ 286 private static final class FeatureWrapper { 287 /** 288 * The feature being wrapped 289 */ 290 private IIQFeature feature; 291 292 /** 293 * Whether the feature no longer exists as of this version 294 */ 295 private boolean negative; 296 297 /** 298 * Creates a new feature wrapper 299 * @param feature the feature 300 * @param negative true if the feature is being removed in this version, false if it is being added 301 */ 302 public FeatureWrapper(IIQFeature feature, boolean negative) { 303 this.feature = feature; 304 this.negative = negative; 305 } 306 } 307 /** 308 * The singleton instance of this class 309 */ 310 private static final IIQVersion INSTANCE = new IIQVersion(); 311 312 /** 313 * The cached version, once determined 314 */ 315 private final AtomicReference<Version> cachedVersion; 316 317 /** 318 * Private constructor for singleton 319 */ 320 private IIQVersion() { 321 this.cachedVersion = new AtomicReference<>(null); 322 } 323 324 /** 325 * Gets the current version of IIQ 326 * @return The current version 327 */ 328 public static Version current() { 329 if (INSTANCE.cachedVersion.get() != null) { 330 return INSTANCE.cachedVersion.get(); 331 } 332 333 String majorVersion = sailpoint.Version.getVersion(); 334 String patchLevel = sailpoint.Version.getPatchLevel(); 335 336 // Default to the most likely version here if we can't determine it 337 Version finalVersion = Version.V8_3; 338 339 Version calculated = determineVersion(majorVersion, patchLevel); 340 if (calculated != null) { 341 finalVersion = calculated; 342 } 343 344 INSTANCE.cachedVersion.set(finalVersion); 345 return finalVersion; 346 } 347 348 /** 349 * Determines the version based on the major version and patch level strings 350 * @param majorVersion The major version string 351 * @param patchLevel The patch level string 352 * @return The determined version, or null if it could not be determined 353 */ 354 private static Version determineVersion(String majorVersion, String patchLevel) { 355 if (Util.nullSafeEq(majorVersion, "7.3")) { 356 return Version.V7_3; 357 } else if (Util.nullSafeEq(majorVersion, "8.0")) { 358 return Version.V8_0; 359 } else if (Util.nullSafeEq(majorVersion, "8.1")) { 360 if (Util.nullSafeEq(patchLevel, "p1")) { 361 return Version.V8_1_P1; 362 } else if (Util.nullSafeEq(patchLevel, "p2")) { 363 return Version.V8_1_P2; 364 } else if (Util.nullSafeEq(patchLevel, "p3")) { 365 return Version.V8_1_P3; 366 } else if (Util.nullSafeEq(patchLevel, "p4")) { 367 return Version.V8_1_P4; 368 } else { 369 return Version.V8_1; 370 } 371 } else if (Util.nullSafeEq(majorVersion, "8.2")) { 372 if (Util.nullSafeEq(patchLevel, "p1")) { 373 return Version.V8_2_P1; 374 } else if (Util.nullSafeEq(patchLevel, "p2")) { 375 return Version.V8_2_P2; 376 } else if (Util.nullSafeEq(patchLevel, "p3")) { 377 return Version.V8_2_P3; 378 } else if (Util.nullSafeEq(patchLevel, "p4")) { 379 return Version.V8_2_P4; 380 } else if (Util.nullSafeEq(patchLevel, "p5")) { 381 return Version.V8_2_P5; 382 } else if (Util.nullSafeEq(patchLevel, "p6")) { 383 return Version.V8_2_P6; 384 } else if (Util.nullSafeEq(patchLevel, "p7")) { 385 return Version.V8_2_P7; 386 } else { 387 return Version.V8_2; 388 } 389 } else if (Util.nullSafeEq(majorVersion, "8.3")) { 390 if (Util.nullSafeEq(patchLevel, "p1")) { 391 return Version.V8_3_P1; 392 } else if (Util.nullSafeEq(patchLevel, "p2")) { 393 return Version.V8_3_P2; 394 } else if (Util.nullSafeEq(patchLevel, "p3")) { 395 return Version.V8_3_P3; 396 } else if (Util.nullSafeEq(patchLevel, "p4")) { 397 return Version.V8_3_P4; 398 } else if (Util.nullSafeEq(patchLevel, "p5")) { 399 return Version.V8_3_P5; 400 } else { 401 return Version.V8_3; 402 } 403 } else if (Util.nullSafeEq(majorVersion, "8.4")) { 404 if (Util.nullSafeEq(patchLevel, "p1")) { 405 return Version.V8_4_P1; 406 } else if (Util.nullSafeEq(patchLevel, "p2")) { 407 return Version.V8_4_P2; 408 } else if (Util.nullSafeEq(patchLevel, "p3")) { 409 return Version.V8_4_P3; 410 } else { 411 return Version.V8_4; 412 } 413 } else if (Util.nullSafeEq(majorVersion, "8.5")) { 414 if (Util.nullSafeEq(patchLevel, "p1")) { 415 return Version.V8_5_P1; 416 } else { 417 return Version.V8_5; 418 } 419 } 420 421 return null; 422 } 423 424 /** 425 * Wraps a feature as a negative (removal) feature 426 * @param feature the feature to wrap 427 * @return the wrapped feature 428 */ 429 private static FeatureWrapper not(IIQFeature feature) { 430 return new FeatureWrapper(feature, true); 431 } 432}