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}