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}