001package com.identityworksllc.iiq.common;
002
003import org.apache.commons.logging.Log;
004import org.apache.commons.logging.LogFactory;
005import sailpoint.tools.CloseableIterator;
006
007import java.util.Optional;
008import java.util.Stack;
009
010/**
011 * A container that will auto-close its contents when it is itself closed.
012 * Closeable items are stored in a {@link Stack} and will be closed in the
013 * reverse order of being added. Errors will be logged and ignored.
014 *
015 * Items implementing {@link AutoCloseable} (most of them) and items implementing
016 * {@link CloseableIterator} are stored separately.
017 *
018 * Example in Beanshell (with dummy methods):
019 *
020 * ```
021 * try {
022 *     Connection dbConnection = closer.add(getDatabaseConnection());
023 *
024 *     PreparedStatement stmt = closer.add(dbConnection.prepareStatement("select id from spt_identity where name = ?"));
025 *     stmt.setString(1, someName);
026 *
027 *     ResultSet results = closer.add(stmt.executeQuery());
028 *     while(results.next()) {
029 *         // do stuff
030 *     }
031 * } finally {
032 *     // All three objects are closed here, in reverse order
033 *     closer.close();
034 * }
035 * ```
036 *
037 * @see AutoCloseable
038 */
039public class Closer implements AutoCloseable {
040    /**
041     * The logger
042     */
043    private static final Log log = LogFactory.getLog(Closer.class);
044
045    /**
046     * A list of items that will be auto-closed, in the reverse of the
047     * order in which they were added
048     */
049    private final Stack<AutoCloseable> autocloseList;
050
051    private final Stack<CloseableIterator<?>> closeableIteratorList;
052
053    /**
054     * Creates a new Closer container with an empty set of items
055     */
056    public Closer() {
057        this.autocloseList = new Stack<>();
058        this.closeableIteratorList = new Stack<>();
059    }
060
061    /**
062     * Creates a new Closer containing the given item
063     * @param cl1 The item to add
064     */
065    public Closer(AutoCloseable cl1) {
066        this();
067        add(cl1);
068    }
069
070    /**
071     * Creates a new Closer containing the given items
072     * @param cl1 The item to add
073     * @param cl2 The item to add
074     */
075    public Closer(AutoCloseable cl1, AutoCloseable cl2) {
076        this(cl1);
077        add(cl2);
078    }
079
080    /**
081     * Creates a new Closer containing the given items
082     * @param cl1 The item to add
083     * @param cl2 The item to add
084     * @param cl3 The item to add
085     */
086    public Closer(AutoCloseable cl1, AutoCloseable cl2, AutoCloseable cl3) {
087        this(cl1, cl2);
088        add(cl3);
089    }
090
091    /**
092     * Creates a new Closer containing the given items
093     * @param cl1 The item to add
094     * @param cl2 The item to add
095     * @param cl3 The item to add
096     * @param cl4 The item to add
097     */
098    public Closer(AutoCloseable cl1, AutoCloseable cl2, AutoCloseable cl3, AutoCloseable cl4) {
099        this(cl1, cl2, cl3);
100        add(cl4);
101    }
102
103    /**
104     * Adds the {@link AutoCloseable} item to the stack to be closed when this container is closed
105     * @param ac The auto-closeable item to add to the stack
106     * @return The passed object, allowing you to open and add in the same line of code
107     * @param <T> Some object extending AutoCloseable
108     */
109    public <T extends AutoCloseable> T add(T ac) {
110        this.autocloseList.push(ac);
111        return ac;
112    }
113
114    /**
115     * Adds the {@link CloseableIterator} item to the stack to be closed when this container is closed
116     * @param ci The CloseableIterator item to add to the stack
117     * @return The passed object, allowing you to open and add in the same line of code
118     */
119    public CloseableIterator<?> add(CloseableIterator<?> ci) {
120        this.closeableIteratorList.push(ci);
121        return ci;
122    }
123
124    /**
125     * Pops each item off the stack and calls close() on it. Any errors will be logged
126     * and ignored. The list will be empty after this method is finished, allowing those
127     * objects to be garbage-collected.
128     *
129     * @throws Exception If anything uncatchable fails
130     */
131    @Override
132    public void close() throws Exception {
133        while(!autocloseList.isEmpty()) {
134            AutoCloseable ac = autocloseList.pop();
135            try {
136                ac.close();
137            } catch(Exception e) {
138                log.error("Unable to close item of type " + ac.getClass(), e);
139            }
140        }
141
142        while(!closeableIteratorList.isEmpty()) {
143            CloseableIterator<?> ci = closeableIteratorList.pop();
144            try {
145                ci.close();
146            } catch(Exception e) {
147                log.error("Unable to close item of type " + ci.getClass(), e);
148            }
149        }
150    }
151
152    /**
153     * Extracts the managed object of the given type. For example, assuming that you
154     * passed a {@link java.sql.Connection} into a {@link Closer}:
155     *
156     * ```
157     * Connection conn = closer.get(Connection.class);
158     * ```
159     * @param requestedType The type of the requested class
160     * @return An optional containing the object, if one matches
161     * @param <T> The output object
162     */
163    public <T extends AutoCloseable> Optional<T> get(Class<T> requestedType) {
164        for(AutoCloseable ac : this.autocloseList) {
165            if (requestedType.isInstance(ac)) {
166                return Optional.of(requestedType.cast(ac));
167            }
168        }
169
170        return Optional.empty();
171    }
172}