From d8fa7da22d09e78c17c8453d9dc30a80dc5c2389 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 17 Nov 2023 14:46:53 +1300 Subject: [PATCH 1/4] Use Driver rather than DriverManager --- .../ebean/datasource/pool/ConnectionPool.java | 49 ++++--------------- .../ebean/datasource/pool/ObtainDriver.java | 44 +++++++++++++++++ .../datasource/pool/ObtainDriverTest.java | 34 +++++++++++++ 3 files changed, 88 insertions(+), 39 deletions(-) create mode 100644 ebean-datasource/src/main/java/io/ebean/datasource/pool/ObtainDriver.java create mode 100644 ebean-datasource/src/test/java/io/ebean/datasource/pool/ObtainDriverTest.java diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 28ca0b5..4eddb95 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -41,7 +41,6 @@ final class ConnectionPool implements DataSourcePool { private final DataSourcePoolListener poolListener; private final Properties connectionProps; private final List initSql; - private final String driver; private final String url; private final String user; private final String schema; @@ -63,6 +62,7 @@ final class ConnectionPool implements DataSourcePool { private final int maxStackTraceSize; private final Properties clientInfo; private final String applicationName; + private final Driver driver; private long lastTrimTime; /** * HeartBeat checking will discover when it goes down, and comes back up again. @@ -105,7 +105,6 @@ final class ConnectionPool implements DataSourcePool { this.captureStackTrace = params.isCaptureStackTrace(); this.maxStackTraceSize = params.getMaxStackTraceSize(); this.url = params.getUrl(); - this.driver = params.getDriver(); this.pstmtCacheSize = params.getPstmtCacheSize(); this.minConnections = params.getMinConnections(); this.maxConnections = params.getMaxConnections(); @@ -139,7 +138,7 @@ final class ConnectionPool implements DataSourcePool { this.connectionProps.setProperty(entry.getKey(), entry.getValue()); } } - checkDriver(); + this.driver = ObtainDriver.driver(params.getDriver(), url); if (!params.isOffline()) { init(); } @@ -156,31 +155,6 @@ private void init() { } } - /** - * Return true if driver has been explicitly configured. - */ - private boolean hasDriver() { - return driver != null && !driver.isEmpty(); - } - - /** - * Check driver exists when explicitly set. - */ - private void checkDriver() { - if (hasDriver()) { - try { - ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); - if (contextLoader != null) { - Class.forName(driver, true, contextLoader); - } else { - Class.forName(driver, true, this.getClass().getClassLoader()); - } - } catch (Throwable e) { - throw new IllegalStateException("Problem loading Database Driver [" + driver + "]: " + e.getMessage(), e); - } - } - } - /** * Accumulate the prepared statement cache metrics as connections are closed. */ @@ -420,7 +394,7 @@ private void checkDataSource() { /** * Initializes the connection we got from the driver. */ - private void initConnection(Connection conn) throws SQLException { + private Connection initConnection(Connection conn) throws SQLException { conn.setAutoCommit(autoCommit); // isolation level is set globally for all connections (at least for H2) and // you will need admin rights - so we do not change it, if it already matches. @@ -454,6 +428,7 @@ private void initConnection(Connection conn) throws SQLException { } } } + return conn; } /** @@ -472,9 +447,7 @@ private Connection createConnection() throws SQLException { private Connection createConnection(Properties properties, boolean notifyIsDown) throws SQLException { try { - final var conn = newConnection(properties); - initConnection(conn); - return conn; + return initConnection(newConnection(properties)); } catch (SQLException ex) { if (notifyIsDown) { notifyDataSourceIsDown(null); @@ -485,7 +458,7 @@ private Connection createConnection(Properties properties, boolean notifyIsDown) private Connection newConnection(Properties properties) throws SQLException { try { - return DriverManager.getConnection(url, properties); + return driver.connect(url, properties); } catch (SQLException e) { notifyLock.lock(); try { @@ -503,7 +476,7 @@ private Connection newConnection(Properties properties) throws SQLException { private Connection switchCredentials(Properties properties) throws SQLException { var copy = new Properties(properties); copy.setProperty("password", password2); - var connection = DriverManager.getConnection(url, copy); + var connection = driver.connect(url, copy); // success, permanently switch to use password2 from now on Log.info("DataSource [{0}] now using alternate credentials", name); fixedCredentials = true; @@ -817,13 +790,11 @@ int pstmtCacheSize() { */ @Override public Connection getConnection(String username, String password) throws SQLException { - Properties props = new Properties(); - props.putAll(connectionProps); + final var props = new Properties(connectionProps); props.setProperty("user", username); props.setProperty("password", password); - Connection conn = DriverManager.getConnection(url, props); - initConnection(conn); - return conn; + final var connection = driver.connect(url, props); + return initConnection(connection); } /** diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ObtainDriver.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ObtainDriver.java new file mode 100644 index 0000000..b52bedc --- /dev/null +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ObtainDriver.java @@ -0,0 +1,44 @@ +package io.ebean.datasource.pool; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; + +final class ObtainDriver { + + /** + * Return the jdbc Driver to use. + */ + static Driver driver(String driver, String url) { + if (driver != null && !driver.isEmpty()) { + return initDriver(driver); + } + try { + return DriverManager.getDriver(url); + } catch (SQLException e) { + throw new IllegalStateException("Unable to obtain Driver", e); + } + } + + private static Driver initDriver(String driver) { + Class driverClass = initDriverClass(driver); + try { + return Driver.class.cast(driverClass.getConstructor().newInstance()); + } catch (Throwable e) { + throw new IllegalStateException("Problem loading Database Driver: " + driver, e); + } + } + + private static Class initDriverClass(String driver) { + try { + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + if (contextLoader != null) { + return Class.forName(driver, true, contextLoader); + } else { + return Class.forName(driver, true, ObtainDriver.class.getClassLoader()); + } + } catch (Throwable e) { + throw new IllegalStateException("Problem loading Database Driver: " + driver, e); + } + } +} diff --git a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ObtainDriverTest.java b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ObtainDriverTest.java new file mode 100644 index 0000000..ae2161e --- /dev/null +++ b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ObtainDriverTest.java @@ -0,0 +1,34 @@ +package io.ebean.datasource.pool; + +import org.junit.jupiter.api.Test; + +import java.sql.Driver; + +import static org.assertj.core.api.Assertions.assertThat; + +class ObtainDriverTest { + + @Test + void h2() { + Driver h2Driver = ObtainDriver.driver(org.h2.Driver.class.getName(), "junk"); + assertThat(h2Driver).isInstanceOf(org.h2.Driver.class); + } + + @Test + void h2Url() { + assertThat(ObtainDriver.driver(null, "jdbc:h2:mem")).isInstanceOf(org.h2.Driver.class); + assertThat(ObtainDriver.driver("", "jdbc:h2:mem")).isInstanceOf(org.h2.Driver.class); + } + + @Test + void postgres() { + Driver h2Driver = ObtainDriver.driver(org.postgresql.Driver.class.getName(), "junk"); + assertThat(h2Driver).isInstanceOf(org.postgresql.Driver.class); + } + + @Test + void postgresUrl() { + Driver h2Driver = ObtainDriver.driver(null, "jdbc:postgresql://127.0.0.1:9999/app"); + assertThat(h2Driver).isInstanceOf(org.postgresql.Driver.class); + } +} From eccf2f3017228151e227359dd70e6f2b9be86ea6 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 17 Nov 2023 15:01:37 +1300 Subject: [PATCH 2/4] Remove the internal ownerConnection() method in favour of getConnection(un,pw) --- .../io/ebean/datasource/pool/ConnectionPool.java | 14 ++------------ .../io/ebean/datasource/test/PostgresInitTest.java | 13 ++++++------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 4eddb95..6571ad1 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -220,7 +220,7 @@ private void initialiseDatabase() throws SQLException { } catch (SQLException e) { Log.info("Obtaining connection using ownerUsername:{0} to initialise database", config.getOwnerUsername()); // expected when user does not exist, obtain a connection using owner credentials - try (Connection ownerConnection = ownerConnection(config.getOwnerUsername(), config.getOwnerPassword())) { + try (Connection ownerConnection = getConnection(config.getOwnerUsername(), config.getOwnerPassword())) { // initialise the DB (typically create the user/role using the owner credentials etc) InitDatabase initDatabase = config.getInitDatabase(); initDatabase.run(ownerConnection, config); @@ -431,16 +431,6 @@ private Connection initConnection(Connection conn) throws SQLException { return conn; } - /** - * Create an un-pooled connection with the given username and password. - */ - private Connection ownerConnection(String username, String password) throws SQLException { - Properties properties = new Properties(connectionProps); - properties.setProperty("user", username); - properties.setProperty("password", password); - return createConnection(properties, true); - } - private Connection createConnection() throws SQLException { return createConnection(connectionProps, true); } @@ -450,7 +440,7 @@ private Connection createConnection(Properties properties, boolean notifyIsDown) return initConnection(newConnection(properties)); } catch (SQLException ex) { if (notifyIsDown) { - notifyDataSourceIsDown(null); + notifyDataSourceIsDown(ex); } throw ex; } diff --git a/ebean-datasource/src/test/java/io/ebean/datasource/test/PostgresInitTest.java b/ebean-datasource/src/test/java/io/ebean/datasource/test/PostgresInitTest.java index 8ad0cf4..d8a8f16 100644 --- a/ebean-datasource/src/test/java/io/ebean/datasource/test/PostgresInitTest.java +++ b/ebean-datasource/src/test/java/io/ebean/datasource/test/PostgresInitTest.java @@ -47,14 +47,13 @@ void test_with_clientInfo() throws SQLException { // clientInfo.setProperty("ClientHostname", "ci-hostname"); DataSourcePool pool = DataSourceBuilder.create() - .setUrl("jdbc:postgresql://127.0.0.1:9999/app") + .url("jdbc:postgresql://127.0.0.1:9999/app") // our application credentials (typically same as db and schema name with Postgres) - .setUsername("app") - .setPassword("app_pass") - // database owner credentials used to create the "app" role as needed - .setOwnerUsername("db_owner") - .setOwnerPassword("test") - .setClientInfo(clientInfo) + .username("app") + .password("app_pass") + .ownerUsername("db_owner") + .ownerPassword("test") + .clientInfo(clientInfo) .build(); try { From 768d590eb8b7f3770aa45278b78aad17b41c1f6c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 17 Nov 2023 15:30:50 +1300 Subject: [PATCH 3/4] Simplify internal ConnectionPool.createConnection() --- .../ebean/datasource/pool/ConnectionPool.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 6571ad1..8db5c4a 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -214,7 +214,7 @@ private void initialiseConnections() throws SQLException { * That is, if we think the username doesn't exist in the DB, initialise the DB using the owner credentials. */ private void initialiseDatabase() throws SQLException { - try (Connection connection = createConnection(connectionProps, false)) { + try (Connection connection = createConnection()) { // successfully obtained a connection so skip initDatabase connection.clearWarnings(); } catch (SQLException e) { @@ -432,23 +432,12 @@ private Connection initConnection(Connection conn) throws SQLException { } private Connection createConnection() throws SQLException { - return createConnection(connectionProps, true); + return initConnection(newConnection()); } - private Connection createConnection(Properties properties, boolean notifyIsDown) throws SQLException { + private Connection newConnection() throws SQLException { try { - return initConnection(newConnection(properties)); - } catch (SQLException ex) { - if (notifyIsDown) { - notifyDataSourceIsDown(ex); - } - throw ex; - } - } - - private Connection newConnection(Properties properties) throws SQLException { - try { - return driver.connect(url, properties); + return driver.connect(url, connectionProps); } catch (SQLException e) { notifyLock.lock(); try { @@ -456,7 +445,7 @@ private Connection newConnection(Properties properties) throws SQLException { throw e; } Log.debug("DataSource [{0}] trying alternate credentials due to {1}", name, e.getMessage()); - return switchCredentials(properties); + return switchCredentials(connectionProps); } finally { notifyLock.unlock(); } From 607c3a49d8e8322606b77523a9f0fa1359287457 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 17 Nov 2023 16:56:32 +1300 Subject: [PATCH 4/4] Refactor internals use DriverDataSource as source of new connections --- .../io/ebean/datasource/DataSourceConfig.java | 21 ++++ .../ebean/datasource/pool/ConnectionPool.java | 95 +++------------ .../datasource/pool/DriverDataSource.java | 110 ++++++++++++++++++ 3 files changed, 150 insertions(+), 76 deletions(-) create mode 100644 ebean-datasource/src/main/java/io/ebean/datasource/pool/DriverDataSource.java diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java index ea61b3d..b91a18f 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java @@ -784,4 +784,25 @@ private int _isolationLevel(String level) { throw new RuntimeException("Transaction Isolation level [" + level + "] is not known."); } + + /** + * Return the connection properties. + */ + public Properties connectionProperties() { + if (username == null) { + throw new DataSourceConfigurationException("DataSource user is not set?"); + } + if (password == null) { + throw new DataSourceConfigurationException("DataSource password is null?"); + } + Properties connectionProps = new Properties(); + connectionProps.setProperty("user", username); + connectionProps.setProperty("password", password); + if (customProperties != null) { + for (Map.Entry entry : customProperties.entrySet()) { + connectionProps.setProperty(entry.getKey(), entry.getValue()); + } + } + return connectionProps; + } } diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 8db5c4a..c814df5 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -25,6 +25,7 @@ */ final class ConnectionPool implements DataSourcePool { + private static final String APPLICATION_NAME = "ApplicationName"; private final ReentrantLock heartbeatLock = new ReentrantLock(false); private final ReentrantLock notifyLock = new ReentrantLock(false); /** @@ -33,15 +34,12 @@ final class ConnectionPool implements DataSourcePool { private final String name; private final AtomicInteger size = new AtomicInteger(0); private final DataSourceConfig config; - private final String password2; /** * Used to notify of changes to the DataSource status. */ private final DataSourceAlert notify; private final DataSourcePoolListener poolListener; - private final Properties connectionProps; private final List initSql; - private final String url; private final String user; private final String schema; private final String heartbeatsql; @@ -62,7 +60,7 @@ final class ConnectionPool implements DataSourcePool { private final int maxStackTraceSize; private final Properties clientInfo; private final String applicationName; - private final Driver driver; + private final DriverDataSource source; private long lastTrimTime; /** * HeartBeat checking will discover when it goes down, and comes back up again. @@ -76,7 +74,6 @@ final class ConnectionPool implements DataSourcePool { private final int waitTimeoutMillis; private final int pstmtCacheSize; private final PooledConnectionQueue queue; - private boolean fixedCredentials; private Timer heartBeatTimer; /** * Used to find and close() leaked connections. Leaked connections are @@ -104,7 +101,6 @@ final class ConnectionPool implements DataSourcePool { this.leakTimeMinutes = params.getLeakTimeMinutes(); this.captureStackTrace = params.isCaptureStackTrace(); this.maxStackTraceSize = params.getMaxStackTraceSize(); - this.url = params.getUrl(); this.pstmtCacheSize = params.getPstmtCacheSize(); this.minConnections = params.getMinConnections(); this.maxConnections = params.getMaxConnections(); @@ -118,27 +114,7 @@ final class ConnectionPool implements DataSourcePool { this.queue = new PooledConnectionQueue(this); this.schema = params.getSchema(); this.user = params.getUsername(); - if (user == null) { - throw new DataSourceConfigurationException("DataSource user is not set? url is [" + url + "]"); - } - String pw = params.getPassword(); - if (pw == null) { - throw new DataSourceConfigurationException("DataSource password is null? url is [" + url + "]"); - } - this.password2 = params.getPassword2(); - this.fixedCredentials = password2 == null; - this.connectionProps = new Properties(); - this.connectionProps.setProperty("user", user); - this.connectionProps.setProperty("password", pw); - - Map customProperties = params.getCustomProperties(); - if (customProperties != null) { - Set> entrySet = customProperties.entrySet(); - for (Entry entry : entrySet) { - this.connectionProps.setProperty(entry.getKey(), entry.getValue()); - } - } - this.driver = ObtainDriver.driver(params.getDriver(), url); + this.source = DriverDataSource.of(name, params); if (!params.isOffline()) { init(); } @@ -151,7 +127,7 @@ private void init() { } initialiseConnections(); } catch (SQLException e) { - throw new DataSourceInitialiseException("Error initialising DataSource with user: " + user + " url:" + url + " error:" + e.getMessage(), e); + throw new DataSourceInitialiseException("Error initialising DataSource with user: " + user + " error:" + e.getMessage(), e); } } @@ -409,7 +385,7 @@ private Connection initConnection(Connection conn) throws SQLException { } if (applicationName != null) { try { - conn.setClientInfo("ApplicationName", applicationName); + conn.setClientInfo(APPLICATION_NAME, applicationName); } catch (SQLClientInfoException e) { Log.error("Error setting clientInfo ApplicationName", e); } @@ -432,35 +408,7 @@ private Connection initConnection(Connection conn) throws SQLException { } private Connection createConnection() throws SQLException { - return initConnection(newConnection()); - } - - private Connection newConnection() throws SQLException { - try { - return driver.connect(url, connectionProps); - } catch (SQLException e) { - notifyLock.lock(); - try { - if (fixedCredentials) { - throw e; - } - Log.debug("DataSource [{0}] trying alternate credentials due to {1}", name, e.getMessage()); - return switchCredentials(connectionProps); - } finally { - notifyLock.unlock(); - } - } - } - - private Connection switchCredentials(Properties properties) throws SQLException { - var copy = new Properties(properties); - copy.setProperty("password", password2); - var connection = driver.connect(url, copy); - // success, permanently switch to use password2 from now on - Log.info("DataSource [{0}] now using alternate credentials", name); - fixedCredentials = true; - properties.setProperty("password", password2); - return connection; + return initConnection(source.getConnection()); } @Override @@ -617,11 +565,10 @@ void returnConnectionReset(PooledConnection pooledConnection) { */ PooledConnection createConnectionForQueue(int connId) throws SQLException { try { - Connection c = createConnection(); - PooledConnection pc = new PooledConnection(this, connId, c); - pc.resetForUse(); + final var pooledConnection = new PooledConnection(this, connId, createConnection()); + pooledConnection.resetForUse(); notifyDataSourceIsUp(); - return pc; + return pooledConnection; } catch (SQLException ex) { notifyDataSourceIsDown(ex); throw ex; @@ -643,6 +590,16 @@ private void reset() { inWarningMode.set(false); } + /** + * Create an un-pooled connection with the given username and password. + *

+ * This uses the default isolation level and autocommit mode. + */ + @Override + public Connection getConnection(String username, String password) throws SQLException { + return initConnection(source.getConnection(username, password)); + } + /** * Return a pooled connection. */ @@ -762,20 +719,6 @@ int pstmtCacheSize() { return pstmtCacheSize; } - /** - * Create an un-pooled connection with the given username and password. - *

- * This uses the default isolation level and autocommit mode. - */ - @Override - public Connection getConnection(String username, String password) throws SQLException { - final var props = new Properties(connectionProps); - props.setProperty("user", username); - props.setProperty("password", password); - final var connection = driver.connect(url, props); - return initConnection(connection); - } - /** * Not implemented and shouldn't be used. */ diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/DriverDataSource.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/DriverDataSource.java new file mode 100644 index 0000000..0853b84 --- /dev/null +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/DriverDataSource.java @@ -0,0 +1,110 @@ +package io.ebean.datasource.pool; + +import io.ebean.datasource.DataSourceConfig; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; + +final class DriverDataSource implements DataSource { + + private final String name; + private final Driver driver; + private final String url; + private final Properties connectionProps; + private final String password2; + private final ReentrantLock lock = new ReentrantLock(); + private boolean fixedCredentials; + + DriverDataSource(String name, Driver driver, String url, Properties properties, String password2) { + this.name = name; + this.driver = driver; + this.url = url; + this.connectionProps = properties; + this.password2 = password2; + this.fixedCredentials = password2 == null; + } + + static DriverDataSource of(String name, DataSourceConfig params) { + final var connectionProps = params.connectionProperties(); + final var driver = ObtainDriver.driver(params.getDriver(), params.getUrl()); + return new DriverDataSource(name, driver, params.getUrl(), connectionProps, params.getPassword2()); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + final var props = new Properties(connectionProps); + props.setProperty("user", username); + props.setProperty("password", password); + return driver.connect(url, props); + } + + @Override + public Connection getConnection() throws SQLException { + try { + return driver.connect(url, connectionProps); + } catch (SQLException e) { + lock.lock(); + try { + if (fixedCredentials) { + throw e; + } + Log.debug("DataSource [{0}] trying alternate credentials due to {1}", name, e.getMessage()); + return switchCredentials(connectionProps); + } finally { + lock.unlock(); + } + } + } + + private Connection switchCredentials(Properties properties) throws SQLException { + var copy = new Properties(properties); + copy.setProperty("password", password2); + var connection = driver.connect(url, copy); + // success, permanently switch to use password2 from now on + Log.info("DataSource [{0}] now using alternate credentials", name); + fixedCredentials = true; + properties.setProperty("password", password2); + return connection; + } + + @Override + public PrintWriter getLogWriter() { + throw new UnsupportedOperationException(); + } + + @Override + public void setLogWriter(PrintWriter out) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLoginTimeout(int seconds) { + throw new UnsupportedOperationException(); + } + + @Override + public int getLoginTimeout() { + throw new UnsupportedOperationException(); + } + + @Override + public Logger getParentLogger() { + throw new UnsupportedOperationException(); + } + + @Override + public T unwrap(Class iface) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWrapperFor(Class iface) { + throw new UnsupportedOperationException(); + } +}