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