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 * encapsulate the variety of inputs in one place. 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}