001package com.identityworksllc.iiq.common.query;
002
003import bsh.EvalError;
004import bsh.This;
005import sailpoint.api.SailPointContext;
006import sailpoint.tools.GeneralException;
007import sailpoint.tools.Util;
008
009import java.sql.Connection;
010import java.util.HashMap;
011import java.util.Map;
012
013/**
014 * The input VO for {@link QueryUtil#iterateQuery(IterateQueryOptions)}, mainly to
015 * collect all of the inputs in one place and allow variable inputs.
016 */
017@SuppressWarnings("unused")
018public final class IterateQueryOptions {
019    /**
020     * A builder for IterateQueryInputs objects.
021     * Contains a nested {@link com.identityworksllc.iiq.common.query.ConnectOptions.ConnectOptionsBuilder}
022     */
023    @SuppressWarnings("unused")
024    public static final class IterateQueryOptionsBuilder {
025
026        private bsh.This bshThis;
027        private String callback;
028        private final ConnectOptions.ConnectOptionsBuilder connectOptionsBuilder;
029        private String query;
030        private Map<String, Object> queryParams;
031
032        private IterateQueryOptionsBuilder() {
033            this.connectOptionsBuilder = ConnectOptions.builder();
034        }
035
036        /**
037         * Validates the inputs and returns a new IterateQueryInputs object
038         *
039         * @return The new object
040         * @throws IllegalArgumentException if any validations fail
041         */
042        public IterateQueryOptions build() throws IllegalArgumentException {
043            if (Util.isNullOrEmpty(callback)) {
044                throw new IllegalArgumentException("Missing required value: 'callback'");
045            }
046            if (bshThis == null) {
047                throw new IllegalArgumentException("Missing required input: Beanshell 'this' object");
048            }
049
050            if (Util.isNullOrEmpty(query)) {
051                throw new IllegalArgumentException("Missing required input: 'query'");
052            }
053
054            ConnectOptions connectOptions = connectOptionsBuilder.build();
055
056            IterateQueryOptions iterateQueryInputs = new IterateQueryOptions();
057            iterateQueryInputs.callback = this.callback;
058            iterateQueryInputs.query = this.query;
059            iterateQueryInputs.bshThis = this.bshThis;
060            iterateQueryInputs.queryParams = this.queryParams;
061            iterateQueryInputs.connectOptions = connectOptions;
062            return iterateQueryInputs;
063        }
064
065        public IterateQueryOptionsBuilder withBshThis(bsh.This bshThis) {
066            this.bshThis = bshThis;
067            connectOptionsBuilder.withBshThis(bshThis);
068            return this;
069        }
070
071        public IterateQueryOptionsBuilder withCallback(String callback) {
072            this.callback = callback;
073            return this;
074        }
075
076        public IterateQueryOptionsBuilder withConnectionParams(Map<String, Object> connectionParams) {
077            connectOptionsBuilder.withConnectionParams(connectionParams);
078            return this;
079        }
080
081        public IterateQueryOptionsBuilder withConnectionRetrievalMethod(String connectionRetrievalMethod) {
082            connectOptionsBuilder.withConnectionRetrievalMethod(connectionRetrievalMethod);
083            return this;
084        }
085
086        public IterateQueryOptionsBuilder withIiqDatabase() {
087            connectOptionsBuilder.withIiqDatabase();
088            return this;
089        }
090
091        public IterateQueryOptionsBuilder withPassword(String password) {
092            connectOptionsBuilder.withPassword(password);
093            return this;
094        }
095
096        public IterateQueryOptionsBuilder withPluginDatabase() {
097            connectOptionsBuilder.withPluginDatabase();
098            return this;
099        }
100
101        public IterateQueryOptionsBuilder withQuery(String query) {
102            this.query = query;
103            return this;
104        }
105
106        public IterateQueryOptionsBuilder withQueryParams(Map<String, Object> queryParams) {
107            this.queryParams = queryParams;
108            return this;
109        }
110
111        public IterateQueryOptionsBuilder withUrl(String url) {
112            connectOptionsBuilder.withUrl(url);
113            return this;
114        }
115
116        public IterateQueryOptionsBuilder withUsername(String username) {
117            connectOptionsBuilder.withUsername(username);
118            return this;
119        }
120    }
121
122    /**
123     * Returns a new builder object for creating an {@link IterateQueryOptions}
124     *
125     * @return The new builder object
126     */
127    public static IterateQueryOptionsBuilder builder() {
128        return new IterateQueryOptionsBuilder();
129    }
130
131    /**
132     * The 'this' callback, used to invoke Beanshell code from Java
133     */
134    private bsh.This bshThis;
135
136    /**
137     * The name of the Beanshell method, which must take a Map parameter,
138     * that will be invoked for each row in the result set.
139     */
140    private String callback;
141    /**
142     * Connection options object
143     */
144    private ConnectOptions connectOptions;
145    /**
146     * The query to run
147     */
148    private String query;
149    /**
150     * Any query parameters to plug in
151     */
152    private Map<String, Object> queryParams;
153
154    private IterateQueryOptions() {
155
156    }
157
158    /**
159     * Copy constructor
160     *
161     * @param other The other object to copy
162     */
163    private IterateQueryOptions(IterateQueryOptions other) {
164        this.bshThis = other.bshThis;
165        this.callback = other.callback;
166
167        this.query = other.query;
168
169        if (other.queryParams != null) {
170            this.queryParams = new HashMap<>(other.queryParams);
171        }
172        this.connectOptions = new ConnectOptions(other.connectOptions);
173    }
174
175    /**
176     * Invoked by {@link QueryUtil#iterateQuery(IterateQueryOptions)} once per row in the
177     * result set
178     *
179     * @param row a Map representing the current row from the result set
180     * @throws GeneralException if anything goes wrong during the callback
181     */
182    public void doCallback(Map<String, Object> row) throws GeneralException {
183        Object[] params = new Object[1];
184        params[0] = row;
185
186        try {
187            bshThis.invokeMethod(this.callback, params);
188        } catch (EvalError e) {
189            throw new GeneralException(e);
190        }
191    }
192
193    /**
194     * Invoked by {@link QueryUtil#iterateQuery(IterateQueryOptions)} once per row in the
195     * result set.
196     *
197     * @param row a Map representing the current row from the result set
198     * @throws GeneralException if anything goes wrong during the callback
199     */
200    public void doParallelCallback(SailPointContext threadContext, Map<String, Object> row) throws GeneralException {
201        if (Thread.currentThread().isInterrupted()) {
202            throw new GeneralException("thread interrupted");
203        }
204        Object[] params = new Object[2];
205        params[0] = threadContext;
206        params[1] = row;
207
208        try {
209            bshThis.invokeMethod(this.callback, params);
210        } catch (EvalError e) {
211            throw new GeneralException(e);
212        }
213    }
214
215    public This getBshThis() {
216        return bshThis;
217    }
218
219    public String getCallback() {
220        return callback;
221    }
222
223    public String getQuery() {
224        return query;
225    }
226
227    public Map<String, Object> getQueryParams() {
228        return queryParams;
229    }
230
231    public Connection openConnection() throws GeneralException {
232        return connectOptions.openConnection();
233    }
234
235    /**
236     * Returns a new {@link IterateQueryOptions} with a different set of query parameters.
237     * This can be used to run the same query for various inputs without having to rebuild
238     * the entire object.
239     *
240     * @param newParams The new query params
241     * @return The new object, identical to this one than swapping out the query params
242     */
243    public IterateQueryOptions withNewQueryParams(Map<String, Object> newParams) {
244        IterateQueryOptions newObject = new IterateQueryOptions(this);
245        newObject.queryParams = newParams;
246        return newObject;
247    }
248}