001package com.identityworksllc.iiq.common;
002
003import sailpoint.object.ProvisioningPlan;
004
005import java.util.Comparator;
006import java.util.HashMap;
007import java.util.Map;
008import java.util.function.IntFunction;
009import java.util.function.ToIntFunction;
010
011/**
012 * Implements comparators to sort account requests within a provisioning plan.
013 *
014 * The comparators safely handle the case where two requests both have the property
015 * (e.g. both are enables), preserving existing ordering where the sort algorithm
016 * does so.
017 *
018 * Additionally, the "first" comparator methods will always retain Create requests
019 * at the start of the sequence.
020 *
021 * The motive behind creating this is the Salesforce connector. It executes the
022 * operations given blindly in the order they are received in the plan. However,
023 * certain operations must always precede others, resulting in a lot of code to
024 * handle this situation.
025 */
026@SuppressWarnings("unused")
027public class PlanComparators {
028
029    /**
030     * A map containing the default ordering for AccountRequests
031     */
032    private static final Map<ProvisioningPlan.AccountRequest.Operation, Integer> ACCOUNT_REQUEST_ORDERING = new HashMap<>();
033
034    /**
035     * A map containing the default ordering for AttributeRequests
036     */
037    private static final Map<ProvisioningPlan.Operation, Integer> ATTRIBUTE_REQUEST_ORDERING = new HashMap<>();
038
039    /**
040     * Performs a default sort of requests, implementing the sequence (Create, Enable, Unlock,
041     * Modify, Lock, Disable, Delete)
042     * @return Sorter
043     */
044    public static Comparator<ProvisioningPlan.AttributeRequest> defaultAttributeSequence() {
045        return Comparator.comparing(v -> ATTRIBUTE_REQUEST_ORDERING.get(v.getOperation()));
046    }
047
048    /**
049     * Performs a default sort of requests, implementing the sequence (Create, Enable, Unlock,
050     * Modify, Lock, Disable, Delete)
051     * @return Sorter
052     */
053    public static Comparator<ProvisioningPlan.AccountRequest> defaultSequence() {
054        return Comparator.comparing(v -> {
055            if (v.getOperation() == null) {
056                return ACCOUNT_REQUEST_ORDERING.get(ProvisioningPlan.AccountRequest.Operation.Modify);
057            } else {
058                return ACCOUNT_REQUEST_ORDERING.get(v.getOperation());
059            }
060        });
061    }
062
063    /**
064     * Moves disables to the start of the sequence, before any other operation except create
065     * @return Sorter
066     */
067    public static Comparator<ProvisioningPlan.AccountRequest> disablesFirst() {
068        return genericSort(ar -> {
069            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
070                // Lowest sort
071                return -100;
072            } else if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Disable)) {
073                // Next lowest sort
074                return -10;
075            } else {
076                // Don't care
077                return 0;
078            }
079        });
080    }
081
082    /**
083     * Moves disables to the end of the sequence
084     * @return Sorter
085     */
086    public static Comparator<ProvisioningPlan.AccountRequest> disablesLast() {
087        return genericSort(ar -> {
088            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
089                // Lowest sort
090                return -100;
091            } else if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Disable)) {
092                // Highest sort
093                return 100;
094            } else {
095                // Don't care
096                return 0;
097            }
098        });
099    }
100
101    /**
102     * Moves enables to the start of the sequence, before any other operation except create
103     * @return Sorter
104     */
105    public static Comparator<ProvisioningPlan.AccountRequest> enablesFirst() {
106        return genericSort(ar -> {
107            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
108                // Lowest sort
109                return -100;
110            } else if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Enable)) {
111                // Next lowest sort
112                return -10;
113            } else {
114                // Don't care
115                return 0;
116            }
117        });
118    }
119
120    /**
121     * Moves enables to the end of the sequence
122     * @return Sorter
123     */
124    public static Comparator<ProvisioningPlan.AccountRequest> enablesLast() {
125        return genericSort(ar -> {
126            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
127                // Lowest sort
128                return -100;
129            } else if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Enable)) {
130                // Highest sort
131                return 100;
132            } else {
133                // Don't care
134                return 0;
135            }
136        });
137    }
138
139    /**
140     * A generic sorter that will use a {@link ToIntFunction} to transform the given AccountRequest into an integer value, then sort those.
141     * @param function The function to transform an AccountRequest into an appropriate integer
142     * @return A comparator to sort AccountRequests by that
143     */
144    public static Comparator<ProvisioningPlan.AccountRequest> genericSort(final ToIntFunction<ProvisioningPlan.AccountRequest> function) {
145        return (o1, o2) -> {
146            int o1i = function.applyAsInt(o1);
147            int o2i = function.applyAsInt(o2);
148
149            return (o1i - o2i);
150        };
151    }
152
153    /**
154     * Moves an account request containing the given attribute request to the start of the
155     * sequence, before any other operation except create.
156     *
157     * @param which The attribute name of the AttributeRequest to float to the top
158     * @return Sorter
159     */
160    public static Comparator<ProvisioningPlan.AccountRequest> specificFirst(final String which) {
161        return genericSort(ar -> {
162            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
163                // Lowest sort
164                return -100;
165            } else if (ar.getAttributeRequest(which) != null) {
166                // Next lowest sort
167                return -10;
168            } else {
169                // Don't care
170                return 0;
171            }
172        });
173    }
174
175    /**
176     * Moves any specific account requests containing the given attribute to the end of the sequence
177     * @param which Which attribute to look for
178     * @return Sorter
179     */
180    public static Comparator<ProvisioningPlan.AccountRequest> specificLast(final String which) {
181        return genericSort(ar -> {
182            if (ar.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Create)) {
183                // Lowest sort
184                return -100;
185            } else if (ar.getAttributeRequest(which) != null) {
186                // Highest sort
187                return 100;
188            } else {
189                // Don't care
190                return 0;
191            }
192        });
193    }
194
195    static {
196        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Create, 0);
197        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Enable, 1);
198        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Unlock, 2);
199        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Modify, 3);
200        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Lock, 4);
201        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Disable, 5);
202        ACCOUNT_REQUEST_ORDERING.put(ProvisioningPlan.AccountRequest.Operation.Delete, 6);
203
204        ATTRIBUTE_REQUEST_ORDERING.put(ProvisioningPlan.Operation.Remove, 0);
205        ATTRIBUTE_REQUEST_ORDERING.put(ProvisioningPlan.Operation.Revoke, 0);
206        ATTRIBUTE_REQUEST_ORDERING.put(ProvisioningPlan.Operation.Retain, 1);
207        ATTRIBUTE_REQUEST_ORDERING.put(ProvisioningPlan.Operation.Set, 2);
208        ATTRIBUTE_REQUEST_ORDERING.put(ProvisioningPlan.Operation.Add, 3);
209    }
210
211}