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}