001package com.identityworksllc.iiq.common.query;
002
003import sailpoint.api.SailPointContext;
004import sailpoint.api.SailPointFactory;
005import sailpoint.server.Environment;
006import sailpoint.tools.GeneralException;
007
008import javax.sql.DataSource;
009import java.sql.Connection;
010import java.sql.SQLException;
011
012/**
013 * Use this class instead of {@link SailPointContext#getJdbcConnection()} to work around
014 * a known glitch.
015 *
016 * The {@link SailPointContext#getJdbcConnection()} method caches the connection that
017 * the context uses. Subsequent calls to the `getJdbcConnection()` on the same context
018 * will return the same object.
019 *
020 * This means that if you `close()` that connection, as you should if you are writing correct
021 * JDBC code, then on the second retrieval, you will get back a wrapper for an already
022 * closed connection. In theory, this ought to just pull a new connection from the pool,
023 * but IIQ has a glitch that prevents this. When you attempt to use the connection,
024 * you will receive a "connection is null" error from deep within DBCP2.
025 *
026 * This utility goes directly to the configured Spring {@link DataSource} to get a pooled connection.
027 *
028 * Note that this glitch doesn't affect Hibernate-based sessions because those already use
029 * their own Hibernate Session Factory that also directly calls to the underlying DataSource.
030 *
031 * TECHNICAL DETAILS:
032 *
033 * The nested layers of underlying connection wrappers is:
034 *
035 *   * SPConnection
036 *   * to ConnectionWrapper
037 *   * to PoolGuardConnectionWrapper
038 *   * to DelegatingConnection
039 *   * to DelegatingConnection (*)
040 *   * to Underlying driver Connection
041 *
042 * The connection marked with a (`*`) is the one that is nulled on close().
043 */
044public class ContextConnectionWrapper {
045
046    /**
047     * Gets a new connection attached to the current SailPointContext.
048     * @return The opened connection
049     * @throws GeneralException if any failures occur opening the connection
050     */
051    public static Connection getConnection() throws GeneralException {
052        return getConnection(null);
053    }
054
055
056    /**
057     * Gets a new connection attached to the given SailPointContext, going directly to the
058     * underlying Spring DataSource object rather than going through the context.
059     *
060     * @param context The context to which the open connection should be attached for logging, or null to use the current thread context
061     * @return The opened connection
062     * @throws GeneralException if any failures occur opening the connection
063     */
064    public static Connection getConnection(SailPointContext context) throws GeneralException {
065        SailPointContext currentContext = SailPointFactory.peekCurrentContext();
066        try {
067            if (context != null) {
068                SailPointFactory.setContext(context);
069            }
070            try {
071                DataSource dataSource = Environment.getEnvironment().getSpringDataSource();
072                if (dataSource == null) {
073                    throw new GeneralException("Unable to return connection, no DataSource defined!");
074                }
075
076                return dataSource.getConnection();
077            } catch (SQLException e) {
078                throw new GeneralException(e);
079            }
080        } finally {
081            SailPointFactory.setContext(currentContext);
082        }
083    }
084
085    /**
086     * Private utility constructor
087     */
088    private ContextConnectionWrapper() {
089
090    }
091}