UcpCredentialsUpdater.java
package com.maybeitssquid.rotatingsecrets.ucp;
import com.maybeitssquid.rotatingsecrets.CredentialRotationException;
import com.maybeitssquid.rotatingsecrets.UpdatableCredential;
import java.sql.SQLException;
import oracle.ucp.UniversalConnectionPoolException;
import oracle.ucp.admin.UniversalConnectionPoolManager;
import oracle.ucp.admin.UniversalConnectionPoolManagerImpl;
import oracle.ucp.jdbc.PoolDataSource;
/**
* Handles credential updates for Oracle Universal Connection Pool (UCP) data sources.
*
* <p>This class implements the {@link UpdatableCredential} interface to receive credential change
* notifications from the {@link com.maybeitssquid.rotatingsecrets.CredentialsProviderService}. When
* credentials are updated, it atomically updates the pool's username and password, then triggers a
* pool refresh to ensure new connections use the updated credentials.
*
* <h2>Credential Update Process</h2>
*
* <ol>
* <li>Acquire a lock on the pool to ensure atomic credential updates
* <li>Update the username and password on the {@link PoolDataSource}
* <li>Call {@link UniversalConnectionPoolManager#refreshConnectionPool(String)} to gracefully
* replace connections with ones using the new credentials
* </ol>
*
* <h2>Thread Safety</h2>
*
* <p>This class is thread-safe. Credential updates are synchronized on the pool data source to
* ensure atomic username/password updates. The pool refresh operation is handled asynchronously by
* UCP.
*
* @see UpdatableCredential
* @see UcpDataSourceConfig
* @see PoolDataSource
*/
public class UcpCredentialsUpdater implements UpdatableCredential<String> {
/** The Oracle UCP pool data source to manage credentials for. */
private final PoolDataSource poolDataSource;
/**
* Creates a new credentials updater for the given Oracle UCP pool.
*
* @param poolDataSource the Oracle UCP PoolDataSource to manage
*/
public UcpCredentialsUpdater(final PoolDataSource poolDataSource) {
this.poolDataSource = poolDataSource;
}
/**
* Updates the credentials used by the Oracle UCP pool and refreshes connections.
*
* <p>This method atomically updates the username and password on the pool, then triggers a pool
* refresh. The refresh operation gracefully replaces existing connections with new ones using the
* updated credentials. Active connections continue to work until they are returned to the pool.
*
* @param username the new database username
* @param credential the new database password
* @throws RuntimeException if the credentials cannot be updated or the pool cannot be refreshed
*/
@Override
public void setCredential(final String username, final String credential) {
final String poolName = this.poolDataSource.getConnectionPoolName();
try {
synchronized (this.poolDataSource) {
this.poolDataSource.setUser(username);
this.poolDataSource.setPassword(credential);
}
final UniversalConnectionPoolManager mgr =
UniversalConnectionPoolManagerImpl.getUniversalConnectionPoolManager();
mgr.refreshConnectionPool(poolName);
} catch (final SQLException e) {
throw new CredentialRotationException(
"Failed to update credentials in poolDataSource " + poolName, e);
} catch (final UniversalConnectionPoolException e) {
throw new CredentialRotationException("Failed to refresh poolDataSource " + poolName, e);
}
}
}