001package com.identityworksllc.iiq.common;
002
003import org.apache.commons.logging.Log;
004import org.apache.commons.logging.LogFactory;
005import sailpoint.api.SailPointContext;
006import sailpoint.object.QueryOptions;
007import sailpoint.object.SailPointObject;
008import sailpoint.tools.GeneralException;
009import sailpoint.tools.Util;
010
011import java.io.Closeable;
012import java.sql.Connection;
013import java.util.Collections;
014import java.util.Iterator;
015import java.util.List;
016
017/**
018 * Utilities to quietly (i.e. without throwing an exception) invoke certain common
019 * operations. This prevents the need to write try/catch boilerplate. In most cases, the
020 * methods will return a sensible default (either null or an empty value) when
021 * the operation fails.
022 *
023 * Exceptions will be logged at info level.
024 */
025public class Quietly {
026    /**
027     * Logger for this class
028     */
029    private static final Log logger = LogFactory.getLog(Quietly.class);
030
031    /**
032     * Closes the thing quietly
033     * @param thing The thing to close
034     */
035    public static void close(Closeable thing) {
036        invoke(thing, Closeable::close);
037    }
038
039    /**
040     * Closes the thing quietly
041     * @param thing The thing to close
042     */
043    public static void close(AutoCloseable thing) {
044        invoke(thing, AutoCloseable::close);
045    }
046
047    /**
048     * Quietly gets a Sailpoint object
049     *
050     * If the idOrName parameter is null, or if the retrieval throws an exception,
051     * this method returns null. This is in addition to the cases where getObject()
052     * already returns null, such as if the object doesn't exist.
053     *
054     * @param context The IIQ context to use for retrieval
055     * @param objectType The SailPointObject class to retrieve
056     * @param idOrName The ID or name of the object
057     * @param <SpoType> On failures
058     * @return The object, or null in the situations described above
059     *
060     * @see SailPointContext#getObject(Class, String)
061     */
062    public static <SpoType extends SailPointObject> SpoType getObject(SailPointContext context, Class<SpoType> objectType, String idOrName) {
063        if (Util.isNotNullOrEmpty(idOrName)) {
064            try {
065                return context.getObject(objectType, idOrName);
066            } catch (GeneralException e) {
067                logger.info("Caught an exception in Quietly.getObject", e);
068            }
069        }
070        return null;
071    }
072
073    /**
074     * Quietly gets a Sailpoint object
075     *
076     * If the idOrName parameter is null, or if the retrieval throws an exception,
077     * this method returns null. This is in addition to the cases where getObjectById()
078     * already returns null, such as if the object doesn't exist.
079     *
080     * @param context The IIQ context to use for retrieval
081     * @param objectType The SailPointObject class to retrieve
082     * @param id The ID of the object
083     * @param <SpoType> On failures
084     * @return The object, or null in the situations described above
085     *
086     * @see SailPointContext#getObjectById(Class, String)
087     */
088    public static <SpoType extends SailPointObject> SpoType getObjectById(SailPointContext context, Class<SpoType> objectType, String id) {
089        if (Util.isNotNullOrEmpty(id)) {
090            try {
091                return context.getObjectById(objectType, id);
092            } catch (GeneralException e) {
093                logger.info("Caught an exception in Quietly.getObjectById", e);
094            }
095        }
096        return null;
097    }
098
099    /**
100     * Quietly gets a Sailpoint object
101     *
102     * If the idOrName parameter is null, or if the retrieval throws an exception,
103     * this method returns null. This is in addition to the cases where getObjectByName()
104     * already returns null, such as if the object doesn't exist.
105     *
106     * @param context The IIQ context to use for retrieval
107     * @param objectType The SailPointObject class to retrieve
108     * @param name The ID of the object
109     * @param <SpoType> On failures
110     * @return The object, or null in the situations described above
111     *
112     * @see SailPointContext#getObjectByName(Class, String)
113     */
114    public static <SpoType extends SailPointObject> SpoType getObjectByName(SailPointContext context, Class<SpoType> objectType, String name) {
115        if (Util.isNotNullOrEmpty(name)) {
116            try {
117                return context.getObjectByName(objectType, name);
118            } catch (GeneralException e) {
119                logger.info("Caught an exception in Quietly.getObjectByName", e);
120            }
121        }
122        return null;
123    }
124
125    /**
126     * Performs the action, logging and swallowing any exceptions
127     * @param input The input object
128     * @param action The action to perform on the input object
129     * @param <T> The type of the input object
130     */
131    public static <T> void invoke(T input, Functions.ConsumerWithError<? super T> action) {
132        try {
133            action.acceptWithError(input);
134        } catch(Throwable t) {
135            logger.error("Caught an exception in Quietly.invoke", t);
136            if (t instanceof Error) {
137                throw (Error)t;
138            }
139        }
140    }
141
142    /**
143     * Performs the action, logging and swallowing any exceptions
144     * @param input The input object
145     * @param input2 The second input object
146     * @param action The action to perform on the input object
147     * @param <In> The type of the input object
148     */
149    public static <In, In2, Out> Out invokeWithOutput(In input, In2 input2, Functions.BiFunctionWithError<? super In, ? super In2, Out> action) {
150        try {
151            return action.applyWithError(input, input2);
152        } catch(Throwable t) {
153            logger.error("Caught an exception in Quietly.invokeWithOutput", t);
154            if (t instanceof Error) {
155                throw (Error)t;
156            }
157        }
158        return null;
159    }
160
161    /**
162     * Performs the action, logging and swallowing any exceptions.
163     *
164     * @param input The input object
165     * @param action The action to perform on the input object
166     * @param <In> The type of the input object
167     * @param <Out> The type of the output object
168     *
169     * @return Either the result of the computation, if it was error-free, or null
170     */
171    public static <In, Out> Out invokeWithOutput(In input, Functions.FunctionWithError<? super In, ? extends Out> action) {
172        try {
173            return action.applyWithError(input);
174        } catch(Throwable t) {
175            logger.error("Caught an exception in Quietly.invokeWithOutput", t);
176            if (t instanceof Error) {
177                throw (Error)t;
178            }
179        }
180        return null;
181    }
182
183    /**
184     * Rolls back the connection quietly
185     * @param connection The conection to roll back
186     */
187    public static void rollback(Connection connection) {
188        invoke(connection, Connection::rollback);
189    }
190
191    /**
192     * Quietly searches for and returns an iterator of Sailpoint objects. If this
193     * set is expected to be large, you should use IncrementalObjectIterator instead
194     * and just deal with the exceptions.
195     *
196     * All exceptions on search will be swallowed.
197     *
198     * This method never returns null, but may return an empty iterator.
199     *
200     * @param context The IIQ context
201     * @param objectType The IIQ object type to search
202     * @param qo The QueryOptions containing the search criteria
203     * @param <SpoType> The SPO type
204     * @return An Iterator containing the search results
205     *
206     * @see SailPointContext#search(Class, QueryOptions)
207     * @see sailpoint.api.IncrementalObjectIterator
208     */
209    public static <SpoType extends SailPointObject> Iterator<SpoType> search(SailPointContext context, Class<SpoType> objectType, QueryOptions qo) {
210        try {
211            Iterator<SpoType> iterator = context.search(objectType, qo);
212            if (iterator != null) {
213                return iterator;
214            }
215        } catch(GeneralException e) {
216            logger.info("Caught an exception in Quietly.search", e);
217        }
218        return Collections.emptyIterator();
219    }
220
221    /**
222     * Quietly searches for and returns an iterator of Object[] projections. If this
223     * set is expected to be large, you should use IncrementalProjectorIterator instead
224     * and just deal with the exceptions.
225     *
226     * All exceptions on search will be swallowed.
227     *
228     * This method never returns null, but may return an empty iterator.
229     *
230     * @param context The IIQ context
231     * @param objectType The IIQ object type to search
232     * @param qo The QueryOptions containing the search criteria
233     * @param columns The columns to return in the projection
234     * @param <SpoType> The SPO type
235     * @return An Iterator containing the search results
236     *
237     * @see SailPointContext#search(Class, QueryOptions, List) 
238     * @see sailpoint.api.IncrementalProjectionIterator
239     */
240    public static <SpoType extends SailPointObject> Iterator<Object[]> search(SailPointContext context, Class<SpoType> objectType, QueryOptions qo, List<String> columns) {
241        try {
242            Iterator<Object[]> iterator = context.search(objectType, qo, columns);
243            if (iterator != null) {
244                return iterator;
245            }
246        } catch(GeneralException e) {
247            logger.info("Caught an exception in Quietly.search", e);
248        }
249        return Collections.emptyIterator();
250    }
251
252}