From 5b31bf73f3905e72e1990d985cee453c65450333 Mon Sep 17 00:00:00 2001 From: Sebb Date: Sat, 18 Jan 2025 22:34:32 +0000 Subject: [PATCH] Normalise EOL --- .gitattributes | 16 + .github/workflows/codeql-analysis.yml | 156 +- .github/workflows/maven.yml | 94 +- src/changes/changes.xml | 3138 +++++----- .../apache/commons/dbcp2/AbandonedTrace.java | 456 +- .../apache/commons/dbcp2/BasicDataSource.java | 5346 ++++++++--------- .../commons/dbcp2/BasicDataSourceFactory.java | 938 +-- .../dbcp2/ConnectionFactoryFactory.java | 152 +- .../dbcp2/DataSourceConnectionFactory.java | 222 +- .../commons/dbcp2/DelegatingConnection.java | 2162 +++---- .../dbcp2/DelegatingPreparedStatement.java | 1432 ++--- .../commons/dbcp2/DelegatingStatement.java | 1632 ++--- .../apache/commons/dbcp2/DriverFactory.java | 160 +- .../dbcp2/DriverManagerConnectionFactory.java | 296 +- .../apache/commons/dbcp2/Jdbc41Bridge.java | 976 +-- .../dbcp2/LifetimeExceededException.java | 88 +- .../commons/dbcp2/ObjectNameWrapper.java | 208 +- .../commons/dbcp2/PoolableConnection.java | 922 +-- .../dbcp2/PoolableConnectionFactory.java | 1746 +++--- .../commons/dbcp2/PoolingConnection.java | 1292 ++-- .../apache/commons/dbcp2/PoolingDriver.java | 540 +- .../java/org/apache/commons/dbcp2/Utils.java | 594 +- .../dbcp2/cpdsadapter/ConnectionImpl.java | 632 +- .../dbcp2/cpdsadapter/DriverAdapterCPDS.java | 1678 +++--- .../cpdsadapter/PooledConnectionImpl.java | 1514 ++--- .../datasources/CPDSConnectionFactory.java | 560 +- .../commons/dbcp2/datasources/CharArray.java | 168 +- .../datasources/InstanceKeyDataSource.java | 2642 ++++---- .../InstanceKeyDataSourceFactory.java | 710 +-- .../KeyedCPDSConnectionFactory.java | 488 +- .../datasources/PerUserPoolDataSource.java | 2354 ++++---- .../commons/dbcp2/datasources/PoolKey.java | 138 +- .../datasources/PooledConnectionManager.java | 136 +- .../datasources/SharedPoolDataSource.java | 476 +- .../dbcp2/datasources/UserPassKey.java | 230 +- .../DataSourceXAConnectionFactory.java | 506 +- .../managed/LocalXAConnectionFactory.java | 780 +-- .../dbcp2/managed/ManagedConnection.java | 664 +- .../PoolableManagedConnectionFactory.java | 226 +- .../dbcp2/managed/TransactionContext.java | 406 +- .../dbcp2/managed/TransactionRegistry.java | 304 +- src/site/xdoc/index.xml | 200 +- .../apache/commons/dbcp2/StackMessageLog.java | 254 +- .../dbcp2/TestAbandonedBasicDataSource.java | 840 +-- .../TestDriverManagerConnectionFactory.java | 314 +- .../apache/commons/dbcp2/TestPStmtKey.java | 524 +- .../apache/commons/dbcp2/TesterResultSet.java | 2336 +++---- .../cpdsadapter/TestDriverAdapterCPDS.java | 892 +-- .../datasources/TestSharedPoolDataSource.java | 1454 ++--- .../dbcp2/datasources/TestUserPassKey.java | 156 +- .../managed/TestBasicManagedDataSource.java | 458 +- .../managed/TestManagedDataSourceInTx.java | 760 +-- 52 files changed, 22691 insertions(+), 22675 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..bec231c194 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +* text=auto diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 86248adde1..dd57c39aee 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,85 +1,85 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '33 9 * * 4' - -permissions: - contents: read - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '33 9 * * 4' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # 3.28.1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild uses: github/codeql-action/autobuild@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # 3.28.1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # 3.28.1 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index f016b7db35..a06783d3ff 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,52 +1,52 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -name: Java CI - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental }} - strategy: - matrix: - java: [ 8, 11, 17, 21, 23 ] - experimental: [false] - include: - - java: 24-ea - experimental: true - - steps: +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Java CI + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + java: [ 8, 11, 17, 21, 23 ] + experimental: [false] + include: + - java: 24-ea + experimental: true + + steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Set up JDK ${{ matrix.java }} + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 - with: - distribution: 'temurin' - java-version: ${{ matrix.java }} - - name: Build with Maven - run: mvn --errors --show-version --batch-mode --no-transfer-progress + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn --errors --show-version --batch-mode --no-transfer-progress diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a6405d9ac2..8accb5e232 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -1,1569 +1,1569 @@ - - - - - - - - Apache Commons DBCP Release Notes - - - - - - Validation query not timing out on connections managed by SharedPoolDataSource. - Validation query not timing out on connections managed by PerUserPoolDataSource. - KeyedCPDSConnectionFactory.validateObject(UserPassKey, PooledObject) ignores timeouts less than 1 second when there is no validation query. - Modernize tests to use JUnit 5 features. - Javadoc is missing its Overview page. - - - Bump org.apache.commons:commons-parent from 78 to 79. - - Removed internal constructors and methods from the package-private class CPDSConnectionFactory; this is binary compatible. - Removed an internal constructor and methods from the package-private class KeyedCPDSConnectionFactory; this is binary compatible. - - - - Avoid object creation when invoking isDisconnectionSqlException #422. - PoolableConnectionFactory.destroyObject() method behaves incorrectly on ABANDONED connection, issue with unhandled AbstractMethodError. DelegatingConnection.abort(Executor) should delegate to Jdbc41Bridge - DelegatingConnection.setSchema(String) should delegate to Jdbc41Bridge. - Fix possible NullPointerException in PoolingConnection.close(). - PerUserPoolDataSource.registerPool() incorrectly replacing a CPDSConnectionFactory into managers map before throwing an IllegalStateException. - Fix PMD UnnecessaryFullyQualifiedName in AbandonedTrace. - Fix PMD UnnecessaryFullyQualifiedName in PoolableCallableStatement. - Fix PMD UnnecessaryFullyQualifiedName in PoolablePreparedStatement. - Fix PMD UnnecessaryFullyQualifiedName in Utils. - Fix PMD UnnecessaryFullyQualifiedName in LocalXAConnectionFactory. - Fix SpotBugs MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT in PerUserPoolDataSource. - Fix SpotBugs MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT in SharedPoolDataSource. - - Add support for ignoring non-fatal SQL state codes #421. - Add @FunctionalInterface to SwallowedExceptionListener. - Add missing Javadoc comments and descriptions. - Add tests, raise the bar for JaCoCo checks. - - Bump org.apache.commons:commons-parent from 66 to 78 #360, #371, #395, #420, #426, #436, #441, #449. - Bump commons-logging:commons-logging from 1.3.0 to 1.3.4 #368, #399, #423. - Bump org.apache.commons:commons-lang3 from 3.14.0 to 3.17.0 #404, #412, #427. - Bump org.hamcrest:hamcrest from 2.2 to 3.0 #410. - Bump org.slf4j:slf4j-simple from 2.0.13 to 2.0.16 #413, #418. - - - - BasicDataSource#setAbandonedUsageTracking has no effect. - PoolingConnection.toString() causes StackOverflowError. - PooledConnectionImpl.destroyObject(PStmtKey, PooledObject) can throw NullPointerException #312. - PoolingConnection.destroyObject(PStmtKey, PooledObject) can throw NullPointerException #312. - Fix examples in src/main/java/org/apache/commons/dbcp2/package-info.java. - - Add property project.build.outputTimestamp for build reproducibility. - Add null guards in DelegatingDatabaseMetaData constructor #352. - Data source bean creation failed due to mismatched return type of setter and getter for connectionInitSqls in BasicDataSource: Add BasicDataSource.setConnectionInitSqls(List). - - Use ReentrantLock in PoolableConnection.close, #591 - Bump commons-lang3 from 3.13.0 to 3.14.0. - Bump commons-parent from 64 to 66. - Bump org.slf4j:slf4j-simple from 2.0.9 to 2.0.12 #349. - - - - Update call sites of deprecated APIs from Apache Commons Pool. - - Add DataSourceMXBean.getUserName() and deprecate getUsername(). - - Bump h2 from 2.2.220 to 2.2.224, #308. - Bump commons-parent from 60 to 64. - Bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9 #301. - Bump org.apache.commons:commons-pool2 from 2.11.1 to 2.12.0. - Bump jakarta.transaction:jakarta.transaction-api from 1.3.1 to 1.3.3. - Bump commons-logging:commons-logging from 1.2 to 1.3.0. - - - - - Fix StackOverflowError in PoolableConnection.isDisconnectionSqlException #123. - - - PerUserPoolDataSourceFactory.getNewInstance(Reference) parsed defaultMaxWaitMillis as an int instead of a long. - - - Reimplement time tracking in AbandonedTrace with an Instant instead of a long. - - - Migrate away from deprecated APIs in Apache Commons Pool. - - - Fix possible NullPointerException in BasicDataSourceFactory.validatePropertyNames(). - - - Fix possible NullPointerException in BasicDataSourceFactory.getObjectInstance(). - - - Connection level JMX queries result in concurrent access to connection objects, causing errors #179. - - - UserPassKey should be Serializable. - - - LifetimeExceededException should extend SQLException. - - - Replace Exception with SQLException in some method signatures (preserves binary compatibility, not source). - - - Don't leak Connections when PoolableConnectionFactory.makeObject() fails to create a JMX ObjectName. - - - Performance: No need for map lookups if we traverse map entries instead of keys. - - - Performance: Refactor to use a static inner class in DataSourceXAConnectionFactory. - - - Reuse pattern of throwing XAException instead of NullPointerException in LocalXAConnectionFactory.LocalXAResource. - - - SpotBugs: An overridable method is called from constructors in PoolableCallableStatement. - - - SpotBugs: An overridable method is called from constructors in PoolablePreparedStatement. - - - Wrong property name logged in ConnectionFactoryFactory.createConnectionFactory(BasicDataSource, Driver). - - - Throw SQLException instead of NullPointerException when the connection is already closed. - - - CPDSConnectionFactory.makeObject() does not need to wrap and rethrow SQLException. - - - PoolingDataSource.close() now always throws SQLException. - - - [StepSecurity] ci: Harden GitHub Actions #282. - - - Fixes typos, missing or misplaced characters, and grammar issues #299. - - - - Add and use AbandonedTrace#setLastUsed(Instant). - - - Add and use Duration versions of now deprecated APIs that use ints and longs. - Internally track durations with Duration objects instead of ints and longs. - See the JApiCmp report for the complete list. - - - Add PMD check to default Maven goal. - - - Add Utils.getDisconnectionSqlCodes() and Utils.DISCONNECTION_SQL_CODES. - - - Make BasicDataSource.getConnectionPool() public. - - - Add github/codeql-action. - - - - Bump actions/cache from 2.1.6 to 3.0.8 #147, #176. - - - Bump actions/checkout from 2.3.4 to 3.0.2 #139, #143, #173. - - - Bump actions/setup-java from 2 to 3.6.0 #229. - - - Bump actions/upload-artifact from 3.1.0 to 3.1.1 #231. - - - Bump checkstyle from 8.44 to 9.3 #121, #130, #149, #158, #190. - - - Bump maven-checkstyle-plugin from 3.1.2 to 3.2.0 #210. - - - Bump commons-pool2 2.10.0 to 2.11.1. - - - Bump junit-jupiter from 5.8.0-M1 to 5.9.1 #125, #136, #157, #203, #218. - - - Bump spotbugs-maven-plugin from 4.3.0 to 4.7.3.0 #140, #154, #161, #178, #192, #200, #204, #213, #234. - - - Bump spotbugs from 4.3.0 to 4.7.3 #124, #133, #151, #164, #177, #189, #214, #230. - - - Bump org.mockito:mockito-core from 3.11.2 to 4.11.0, #128, #138, #152, #175, #188. #193, #208, #215, #232, #235, #246, #252. - - - Bump maven-javadoc-plugin from 3.3.0 to 3.4.1 #131, #184. - - - Bump maven-pmd-plugin from 3.14.0 to 3.19.0 #132, #172, #195. - - - Bump pmd from 6.44.0 to 6.52.0. - - - Bump narayana-jta from 5.12.0.Final to 5.12.7.Final #134, #156, #163, #185, #197. - - - Bump japicmp-maven-plugin from 0.15.3 to 0.17.1 #137, #166, #174, #211, #238. - - - Bump h2 from 1.4.200 to 2.2.220 #153, #183, #196, #287. - Update SQL for migration from H2 1.4.200 to 2.0.204 where "KEY" and "VALUE" are now reserved keywords. - - - Bump jboss-logging from 3.4.2.Final to 3.4.3.Final #162. - - - Bump slf4j-simple from 1.7.30 to 1.7.36 #169. - - - Bump commons-parent from 52 to 60 #180, #219, #254, #278. - - - Bump JaCoCo from 0.8.7 to 0.8.8. - - - Bump maven-surefire-plugin 2.22.2 to 3.0.0-M7. - - - Bump apache-rat-plugin 0.13 to 0.14. - - - Bump commons-lang3 from 3.12 to 3.13.0. - - - - - - Add and reuse Constants.KEY_USER and Constants.KEY_PASSWORD. - - - Add and reuse DataSourceMXBean. - - - Add and reuse DriverAdapterCPDS.{get|set}DurationBetweenEvictionRuns(), deprecate {get|set}TimeBetweenEvictionRunsMillis(long). - - - Add and reuse DriverAdapterCPDS.{get|set}MinEvictableIdleDuration(), deprecate {get|set}MinEvictableIdleTimeMillis(int). - - - Add and reuse CPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). - - - Add and reuse KeyedCPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). - - - Add and reuse KeyedCPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). - - - Add and reuse InstanceKeyDataSource.{get|set}DefaultMaxWait(Duration), deprecate {get|set}DefaultMaxWaitMillis(long). - - - - Fix test random failure on TestSynchronizationOrder.testInterposedSynchronization, #84. - - - ManagedConnection must clear its cached state after transaction completes, #75. - - - Minor Improvements #78. - - - Use abort rather than close to clean up abandoned connections. - - - Performance Enhancement: Call toArray with Zero Array Size #20. - - - Avoid exposing password via JMX #38. - - - Remove redundant initializers #98. - - - Simplify test assertions #100, #113. - - - DataSource implementations do not implement Wrapper interface correctly #93. - - - Replace FindBugs with SpotBugs. - - - DataSourceConnectionFactory.getUserPassword() may expose internal representation by returning DataSourceConnectionFactory.userPassword. - - - DataSourceXAConnectionFactory.getUserPassword() may expose internal representation by returning DataSourceXAConnectionFactory.userPassword. - - - DriverAdapterCPDS.getPasswordCharArray() may expose internal representation by returning DriverAdapterCPDS.userPassword. - - - new org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory(TransactionManager, XADataSource, String, char[], TransactionSynchronizationRegistry) may expose internal representation by storing an externally mutable object into DataSourceXAConnectionFactory.userPassword. - - - org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory.setPassword(char[]) may expose internal representation by storing an externally mutable object into DataSourceXAConnectionFactory.userPassword. - - - org.apache.commons.dbcp2.PStmtKey.getColumnIndexes() may expose internal representation by returning PStmtKey.columnIndexes. - - - org.apache.commons.dbcp2.PStmtKey.getColumnNames() may expose internal representation by returning PStmtKey.columnNames. - - - Use Collections.synchronizedList() Instead Of Vector #101. - - - Simplify and inline variables #99. - - - Update PoolKey#toString() to avoid revealing a user name is here. - - - Internal package private UserPassKey class stores its user name as a char[] as it already does the password. - - - Performance of DelegatingConnection.prepareStatement(String) regressed enormously in 2.8.0 compared to 1.4. - DelegatingConnection should also cache connection schema string to avoid calling the Connection#getSchema() for each key creation. - DelegatingConnection should also cache connection catalog string to avoid calling the Connection#getCatalog() for each key creation. - - - BasicDataSource should test for the presence of a security manager dynamically, not once on initialization. - - - - Bump mockito-core from 3.5.11 to 3.11.2 #66, #72, #77, #85, #91, #105, #110, #116. - - - Bump actions/checkout from v2.3.2 to v2.3.4 #65, #74. - - - Bump actions/cache from v2 to v2.1.6 #90, #108. - - - Bump commons-pool2 from 2.8.1 to 2.9.0. - - - Bump actions/setup-java from v1.4.2 to v2 #69. - - - Bump japicmp-maven-plugin from 0.14.3 to 0.15.2 #71, #82. - - - Bump maven-pmd-plugin from 3.13.0 to 3.14.0 #76. - - - Bump japicmp-maven-plugin from 0.14.4 to 0.15.3, #83. - - - Bump Hamcrest 1.3 -> 2.2 #70. - - - Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #88. - - - Bump junit-jupiter from 5.7.0 to 5.8.0-M1, #89, #106. - - - Bump narayana-jta from 5.10.6.Final to 5.12.0.Final #103, #111. - - - Bump maven-javadoc-plugin from 3.2.0 to 3.3.0 #107. - - - Bump commons.jacoco.version 0.8.6 -> 0.8.7. - - - Bump jboss-logging from 3.4.1.Final to 3.4.2.Final #109. - - - Bump org.jboss:jboss-transaction-spi from 7.6.0.Final to 7.6.1.Final. - - - Bump commons-pool2 from 2.9.0 to 2.10.0. - - - Bump checkstyle to 8.44. - - - Bump spotbugs from 4.2.3 to 4.3.0 #117. - - - Bump spotbugs-maven-plugin from 4.2.3 to 4.3.0 #118. - - - - - - Fix BasicManagedDataSource leak of connections opened after transaction is rollback-only #39. - - - Add clearStatementPoolOnReturn #42. - - - Add start, restart methods to BasicDataSource. #50. - - - - NPE when creating a SQLExceptionList with a null list. - - - Fix DelegatingConnection readOnly and autoCommit caching mechanism #35. - - - Fix regression introduced by unreleased code clean-up #63. - - - Update to PR#36 - PrepareStatement and prepareCall methods are extracted #37. - - - Do not display credentials in DriverAdapterCPDS.toString(). - - - Do not display credentials in DelegatingConnection.toString(). - - - Do not display credentials in DriverConnectionFactory.toString(). - - - Do not display credentials in PoolKey.toString(). - - - Do not display credentials in UserPassKey.toString(). - - - - Update Apache Commons Pool from 2.7.0 to 2.8.1, #48. - - - Update tests from H2 1.4.199 to 1.4.200. - - - Update tests from Mockito 3.0.0 to 3.5.11 #47, #60, #64. - - - Update tests from jboss-logging 3.4.0.Final to 3.4.1.Final. - - - Update tests from narayana-jta 5.9.5.Final to 5.10.6.Final, #61. - - - Update tests from junit-jupiter 5.5.1 to 5.7.0 #62. - - - Update tests from org.slf4j:slf4j-simple 1.7.26 to 1.7.30. - - - Update build from com.github.siom79.japicmp:japicmp-maven-plugin 0.13.1 to 0.14.3. - - - Update build from maven-javadoc-plugin 3.1.1 to 3.2.0. - - - Update build from maven-pmd-plugin 3.12.0 to 3.13.0. - - - Update org.apache.commons:commons-parent from 48 to 51. - - - Update jacoco-maven-plugin from 0.8.4 to 0.8.6. - - - Update maven-checkstyle-plugin from 3.0.0 to 3.1.1. - - - Update actions/checkout from v1 to v2.3.2, #44, #51. - - - Update actions/setup-java from v1.4.0 to v1.4.2 #58. - - - - - - ManagedDataSource#close() should declare used exceptions. - - - Add a ConnectionFactory class name setting for BasicDataSource.createConnectionFactory() #33. - - - Add missing Javadocs. - - - - Wrong JMX base name derived in BasicDataSource#updateJmxName. - - - Avoid NPE when calling DriverAdapterCPDS.toString(). - - - java.util.IllegalFormatException while building a message for a SQLFeatureNotSupportedException in Jdbc41Bridge.getObject(ResultSet,String,Class). - - - Fix Javadoc link in README.md #21. - - - - Close ObjectOutputStream before calling toByteArray() on underlying ByteArrayOutputStream #28. - - - Upgrade to JUnit Jupiter #19. - - - Fix tests on Java 11. - - - Update Apache Commons Pool from 2.6.1 to 2.6.2. - - - Add 'jmxName' property to web configuration parameters listing. - - - Update Apache Commons Pool from 2.6.2 to 2.7.0. - - - Make org.apache.commons.dbcp2.AbandonedTrace.removeTrace(AbandonedTrace) null-safe. - - - org.apache.commons.dbcp2.DelegatingStatement.close() should try to close ALL of its result sets even when an exception occurs. - - - org.apache.commons.dbcp2.DelegatingConnection.passivate() should close ALL of its resources even when an exception occurs. - - - org.apache.commons.dbcp2.PoolablePreparedStatement.passivate() should close ALL of its resources even when an exception occurs. - - - org.apache.commons.dbcp2.PoolableCallableStatement.passivate() should close ALL of its resources even when an exception occurs. - - - - Update tests from org.mockito:mockito-core 2.28.2 to 3.0.0. - - - Update tests from H2 1.4.198 to 1.4.199. - - - Update tests from com.h2database:h2 1.4.197 to 1.4.199. - - - Update tests from org.jboss.narayana.jta:narayana-jta 5.9.2.Final to 5.9.5.Final. - - - Update tests from org.jboss.logging:jboss-logging 3.3.2.Final to 3.4.0.Final. - - - Update tests from org.mockito:mockito-core 2.24.0 to 2.28.2. - - - Update tests from org.mockito:mockito-core 2.28.2 to 3.0.0. - - - - - Allow for manual connection eviction. - - - Allow DBCP to register with a TransactionSynchronizationRegistry for XA cases. - - - Make defensive copies of char[] passwords. - - - Do not try to register synchronization when the transaction is no longer active. - - - Do not double returnObject back to the pool if there is a transaction context with a shared connection. - - - Allow DBCP to work with old Java 6/JDBC drivers without throwing AbstractMethodError. - - - Add some toString() methods for debugging (never printing passwords.) - - - BasicManagedDataSource needs to pass the TSR with creating DataSourceXAConnectionFactory. - - - Add getters to some classes. - - - org.apache.commons.dbcp2.DriverManagerConnectionFactory should use a char[] instead of a String to store passwords. - - - Update Apache Commons Pool from 2.6.0 to 2.6.1. - - - - - Update Java requirement from version 7 to 8. - - - Support JDBC 4.2. - - - Support default schema in configuration. - - - Examines 'SQLException's thrown by underlying connections or statements for fatal (disconnection) errors. - - - Change default for fail-fast connections from false to true. - - - Prepared statement keys should take a Connection's schema into account. - - - Increase test coverage. - - - Update Apache Commons Pool from 2.5.0 to 2.6.0. - - - Avoid exceptions when closing a connection in mutli-threaded use case. - - - - - Connection leak during XATransaction in high load. - - - Drop Ant build. - - - Ensure DBCP ConnectionListener can deal with transaction managers which invoke rollback in a separate thread. - - - org.apache.commons.dbcp2.PStmtKey should make copies of given arrays in constructors. - - - Remove duplicate code in org.apache.commons.dbcp2.cpdsadapter.PStmtKeyCPDS. - - - Add support for pooling CallableStatements to the org.apache.commons.dbcp2.cpdsadapter package. - - - Deprecate use of PStmtKeyCPDS in favor of PStmtKey. - - - org.apache.commons.dbcp2.DataSourceConnectionFactory should use a char[] instead of a String to store passwords. - - - org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory should use a char[] instead of a String to store passwords. - - - org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS should use a char[] instead of a String to store passwords. - - - org.apache.commons.dbcp2.datasources.CPDSConnectionFactory should use a char[] instead of a String to store passwords. - - - org.apache.commons.dbcp2.datasources internals should use a char[] instead of a String to store passwords. - - - org.apache.commons.dbcp2.datasources.InstanceKeyDataSourceFactory.closeAll() does not close all. - - - - - AbandonedTrace.getTrace() contains race condition. - - - Avoid javax.management.InstanceNotFoundException on shutdown when a bean is not registered. Closes #9. - - - Make constant public: org.apache.commons.dbcp2.PoolingDriver.URL_PREFIX. - - - DriverAdapterCPDS.setUser(), setPassword(), and getPooledConnection() with null arguments throw NullPointerExceptions when connection properties are set. - - - Add API org.apache.commons.dbcp2.datasources.PerUserPoolDataSource.clear(). - - - NPE for org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS.setConnectionProperties(null). - - - The method org.apache.commons.dbcp2.PoolingDriver.getConnectionPool(String) does not tell you which pool name is not registered when it throws an exception. - - - - - Update Apache Commons Pool from 2.4.2 to 2.5.0. - - - OSGi declarations contain multiple import headers for javax.transaction. - - - Wrong parameter name in site documentation for BasicDataSource Configuration Parameters. - - - Add jmxName to properties set by BasicDataSourceFactory. This - enables container-managed pools created from JNDI Resource - definitions to enable JMX by supplying a valid root JMX name. - - - NullPointerException thrown when calling ManagedConnection.isClosed(). - - - InvalidateConnection can result in closed connection returned by getConnection. - - - Complete the fix for DBCP-418, enabling PoolableConnection class to load in environments - (such as GAE) where the JMX ManagementFactory is not available. - - - Add constructor DriverManagerConnectionFactory(String). - - - Ensure that the cacheState setting is used when statement pooling is - disabled. - - - Ensure that setSoftMinEvictableIdleTimeMillis is used when working with - BasicDataSource. - - - Correct the name of the configuration attribute - softMinEvictableIdleTimeMillis. - - - Avoid potential infinite loops when checking if an SQLException is fatal - for a connection or not. - - - Expand the fail-fast for fatal connection errors feature to include - managed connections. - - - Correct a typo in the method name - PoolableConnectionFactory#setMaxOpenPreparedStatements. The old method - remains but is deprecated so not to break clients currently using the - incorrect name. - - - Refactoring to prepare for a future patch to enable pooling of all - prepared and callable statements in PoolingConnection. - - - Ensure that a thread's interrupt status is visible to the caller if the - thread is interrupted during a call to - PoolingDataSource.getConnection(). - - - Make it simpler to extend BasicDataSource to allow sub-classes to - provide custom GenericObjectPool implementations. - - - When using a BasicDataSource, pass changes related to the handling of - abandoned connections to the underlying pool so that the pool - configuration may be updated dynamically. - - - Enable pooling of all prepared and callable statements - inPoolingConnection. - - - - - Updated pool version to 2.4.2. The fix for POOL-300 may cause DBCP - users to see more reports of abandoned connections (if removal and logging - are configured). Prior to the fix for POOL-300, the PrintWriter used to log - abandoned connection stack traces was not being flushed on each log event. - - - Added BasicDataSource abandonedUsageTracking property missing from BasicDataSourceFactory. - - - SharedPoolDataSource getConnection fails when testOnBorrow is set with - a null validation query. - - - Nested connections in a transaction (local) throws null pointer. - - - BasicDataSource does not set disconnectionSql properties on its PoolableConnectionFactory. - - - - - InstanceKeyDataSource discards native SQLException when given password does not match - password used to create the connection. - - - Update Apache Commons Logging to 1.2 from 1.1.3. - - - Correct some Javadoc references to Apache Commons Pool 2 classes that - have changed names since Pool 1.x. - - - Do not ignore the configured custom eviction policy when creating a - BasicDataSource. - - - Added invalidateConnection method to BasicDataSource. - - - Unsuccessful Connection enlistment in XA Transaction ignored by TransactionContext. - - - Made expired connection logging configurable in BasicDataSource. Setting - logExpiredConnections to false suppresses expired connection log messages. - - - Made Datasources implement AutoCloseable. - - - Added fastFailValidation property to PoolableConnection, configurable in - BasicDataSource. When set to true, connections that have previously thrown - fatal disconnection errors will fail validation immediately (no driver calls). - - - Changed BasicDataSource createDataSource method to ensure that initialization - completes before clients get reference to newly created instances. - - - Fixed connection leak when SQLException is thrown while enlisting an XA - transaction. - - - Setting jmxName to null should suppress JMX registration of connection - and statement pools. - - - Eliminated synchronization in BasicDataSource getNumActive, getNumIdle methods. - - - Added property name verification to BasicDataSourceFactory. References including - obsolete or unrecognized properties now generate log messages. - - - - - Small performance improvements when returning connections to the pool. - - - Fixed DelegatingStatement close to ensure closed statements do not retain references - to pooled prepared statements. Due to finalization code added in 2.0, this was causing - pooled prepared statements to be closed by GC while in use by clients. - - - Added check in PoolingDataSource constructor to ensure that the connection factory - and pool are properly linked. - - - Fixed connection leak when managed connections are closed during transactions. - - - Enable PoolableConnection class to load without JMX. - - - - - BasicManagedDataSource - unregister from JMX on close(). - - - Log validation failures of poolable connections. - - - DelegatingStatement.close() fails with NPE if statement is null - - - CPDSConnectionFactory.validateObject(Object) ignores Throwable. - - - Provide a new option (cacheState) to cache current values of autoCommit - and readOnly so database queries are not required for every call to the - associated getters. This option is enabled by default. - - - Removed unnecessary synchronisation in BasicDataSource#createDataSource. - - - The Java package name has been changed from org.apache.commons.dbcp to - org.apache.commons.dbcp2. - - - Update to Commons Pool 2 (based on java.util.concurrent) to provide - pooling functionality. - - - Updated source code for Java 1.6 (added @Override & @Deprecated - annotations). - - - Removed JOCL support. - - - Remove deprecated SQLNestedException. - - - Fix threading issues with accessToUnderlyingConnectionAllowed attribute - of PoolingDriver which is used to support unit testing. - - - BasicDataSource instances are now exposed via JMX. All the configuration - properties are available as is the connection pool and the statement - pools (if statement pooling is enabled). - - - Fix thread safety issues in the SharedPoolDataSource and the - PerUserPoolDataSource. - - - Allow accessToUnderlyingConnectionAllowed to be configured when - configuration takes place via JNDI in a JavaEE container. - - - Fix threading issue when using multiple instances of the - SharedPoolDataSource concurrently. - - - Ensure that the close state of a pooled connection and the underlying - connection is consistent when the underlying connection is closed as a - result of an error condition. - - - Make all mutable fields private. - - - Return BasicDataSource rather than DataSource from - BasicDataSourceFactory so a cast is not required to use BasicDataSource - specific methods. - - - The equals() implementations of the DelegatingXxx classes are now - symmetric. There are some important API changes underlying this fix. - Firstly, two DelegatingXxx instances are no longer considered equal if - they have the same innermost delegate. Secondly, a DelegatingXxx - instance is not considered equal to its innermost delegate. The - getInnermostDelegateInternal() method has been made public (but remains - part of the internal API) to allow classes extending this implementation - to access the innermost delegate when required. - - - Expose the new Pool 2 property evictionPolicyClassName to enable more - sophisticated eviction strategies to be used. - - - Add support for pooling PreparedStatements that use auto-generated keys. - - - Enable JDBC resources that are no longer referenced by client code to be - eligible for garbage collection. - - - Enable DBCP to work with a SecurityManager such that only DBCP needs to - be granted the necessary permissions to communicate with the database. - - - Correct path to Javadoc overview in build.xml. - - - The default values for readOnly, autoCommit and transactionIsolation are - now taken from the JDBC driver. No calls to setReadOnly(), - setAutoCommit() or setTransactionIsolation() will be made for a newly - borrowed connection unless a default is explicitly configured and the - connection is currently using a different setting. - - - Register pooled connections with JMX so that they may be forcibly closed - via JMX if required. - - - Modify SharedPoolDataSource and PerUserPoolDataSource to expose all of - the configuration properties of the underlying connection pool(s). This - represents a significant refactoring of these classes and a number of - property names have changed as a result. - - - Provide an option to control if autoCommit is always set to true when a - connection is returned to the connection pool. - - - Provide an option to control if rollback is called when a connection is - returned to the poll with autoCommit disabled. - - - Provide an option to set the default query timeout. - - - Connection.isValid() should not throw an SQLException if the connection - is closed. - - - Use Connection.isValid() to validate connections unless a validation - query is explicitly configured. Note that this means it is no longer - necessary for a validation query to be specified in order for validation - to take place. When testing with Oracle, this resulted in database - validation being approximately 7 times faster. - - - Add support for validation testing database connections on creation. - - - - - Correct the documentation for the maxOpenPreparedStatements parameter - and review the use of the phrase non-positive throughout the - documentation and javadoc, replacing it with negative where that is the - correct definition to use. - - - Avoid multiple calls to Connection.getAutoCommit() in - PoolableConnectionFactory.passivateObject() as it could be an expensive - call. - - - Use one line per statement for methods with multiple statements rather - than using a single line. - - - Expose all of the AbandonedConfig properties through a BasicDataSource. - - - Correct implementation of DelegatingConnection.isWrapperFor() so it - works correctly with older JDBC drivers. - - - Correct implementation of DelegatingStatement.isWrapperFor(). Also fix - DelegatingDatabaseMetaData.isWrapperFor() and - DelegatingResultSet.isWrapperFor() that had the same problem. - - - LocalXAConnectionFactory does not properly check if Xid is equal to - currentXid when resuming which may result in an XAException. - - - Ensure that the XAConnection is closed when the associated Connection is - closed. - - - Clarify Jaavdoc for isClosed() method of PoolableConnection. - - - Avoid NullPointerException and throw an XAException if an attempt is - made to commit the current transaction for a connection when no - transaction has been started. - - - Using batchUpdate() should not invalidate the PreparedStatement when it - is returned to the pool. - - - Improve documentation for JNDI example using BasicDataSource. - - - - - Exposed GenericObjectPool's softMinEvictableIdleTimeMillis property for - configuration and use by BasicDataSource. - - - Made equals reflexive in DelegatingStatement (and subclasses), DelegatingMetaData, - DelegatingResultSet and PoolingDriver#PoolGuardConnectionWrapper. - - - Modified createDataSource method in BasicDataSource to ensure that GenericObjectPool - Evictor tasks are not started and orphaned when BasicDataSource encounters errors on - initialization. Prior to this fix, when minIdle and timeBetweenEvictionRunsMillis - are both positive, Evictors orphaned by failed initialization can continue to - generate database connection requests. This issue is duplicated by DBCP-339 - and DBCP-93. - - - Changed DelegatingDatabaseMetaData to no longer add itself to the AbandonedTrace - of its parent connection. This was causing excessive memory consumption and was - not necessary, as resultsets created by DelegatingDatabaseMetaData instances are - attached to the parent connection's trace on creation. Also fixes DBCP-352. - - - Modified execute methods of Statement objects to ensure that whenever - a statement is used, the lastUsed property of its parent connection is - updated. - - - Correctly implemented the option to configure the class loader used to load - the JDBC driver. - - - LIFO configuration option has been added to BasicDataSource. When set - to true (the default), the pool acts as a LIFO queue; setting to false - causes connections to enter and exit to pool in FIFO order. - - - Test transitive dependencies brought in by geronimo-transaction created - version conflicts (commons logging and junit). These have been explicitly - excluded in the POM. - - - BasicDataSourceFactory incorrectly used "initConnectSqls" in versions - 1.3 and 1.4 of DBCP as the property name for connectionInitSqls. - Online docs for 1.3/1/4 have been updated to reflect this inconsistency. - The BasicDataSourceFactory property name has been changed to "connectInitSqls" - to match the online docs and the BasicDataSource property name. - - - - - Eliminated poolKeys cache from PerUserPoolDataSource. - - - Eliminated userKeys LRUMap cache from SharedPoolDataSource. - - - Made private fields final where possible. - - - PerUserPoolDataSource.getPooledConnectionAndInfo multi-threading bug. - - - Remove throws clause from method that does not throw an exception. - - - Remove code that catches and ignores Exceptions when calling - PooledConnection.removeConnectionEventListener(ConnectionEventListener) - as the method does not throw any Exceptions. - - - Remove impossible null check. - - - Renamed variables with duplicate names in different scopes. - - - Clarified javadoc for BasicDataSource close() method. - - - Made PoolingConnection pool CallableStatements. When BasicDataSource's - poolPreparedStatements property is true, CallableStatements are now - pooled along with PreparedStatements. The maxOpenPreparedStatements - property limits the combined number of Callable and Prepared statements - that can be in use at a given time. - - - Use an API specific exception for logging abandoned objects to make - scanning the logs for these exceptions simpler and to provide a better - message that includes the creation time of the abandoned object. - - - Ensure Statement.getGeneratedKeys() works correctly with the CPDS - adapter. - - - Removed incorrectly advertised ClassNotFoundException from - JOCLContentHandler.ConstructorDetails.createObject(). - - - Make the class loader used to load the JDBC driver configurable for the - BasicDatasource. - - - Handle user password changes for InstanceKeyDataSources. - - - Made XADataSource configurable in BasicManagedDataSource. - - - Added PoolableManagedConnection and PoolableManagedConnectionFactory so that - pooled managed connections can unregister themselves from transaction registries, - avoiding resource leaks. - - - Added connectionProperties property to DriverAdapterCPDS. - - - Added a validationQueryTimeout configuration parameter to BasicDataSource - allowing the user to specify a timeout value (in seconds) for connection - validation queries. - - - Added a connectionInitSqls configuration parameter to BasicDataSource - allowing the user to specify a collection of SQL statements to execute - one time when a physical database connection is first opened. - - - PoolableConnectionFactory.makeObject() is no longer synchronized. This - provides improved response times when load spikes at the cost of a - faster rise in database server load. This change was made as a partial - fix for DBCP-212. The synchronization changes in Commons Pool 1.5 complete - the fix for this issue. - - - Reverted DelegatingConnection close to 1.2.2 version to ensure - open statements are closed before the underlying connection is closed. - - - Refactor DelegatingConnection and ManagedConnection enable overridden - equals() and hashcode() to work correctly. - - - Add a DelegatingDatabaseMetaData to track ResultSets returned from - DatabaseMetaData objects. - - - Modified BasicDataSourceFactory to complete initialization of the pool - by creating initialSize connections rather than leaving this to lazy - initialization when the pool is used. - - - Eliminated masked _stmt field in descendents of DelegatingStatement. - - - Modified DBCP sources to support compilation under JDK 1.4-1.6 - using Ant flags to do conditional compilation. - - - Added a static initializer to BasicDatasource that calls - DriverManager.getDrivers() to force initialization before we ever do - anything that might use Class.forName() to load (and register) a JDBC - driver. - - - Eliminated direct System.out calls in AbandonedTrace. - - - Modified DelegatingStatement close to clear open batches. - - - Eliminated unused private "parent" field in AbandonedTrace. - - - Fixed errors handling boolean-valued Reference properties in - InstanceKeyObjectFactory, DriverAdapterCPDS that were causing - testOnBorrow and poolPreparedStatements properties to be incorrectly - set when creating objects from javax.naming.Reference instances. - - - Made private instance fields of AbandonedTrace volatile (parent, - createdBy, lastUsed, createdTime) or final (trace). - - - Narrowed synchronization in AbandonedTrace to resolve an Evictor deadlock. - - - Corrected Javadoc to state that getLoginTimeout and setLoginTimeout are - NOT supported by BasicDataSource. - - - Added Maven 2 pom.xml. Removed a block of code from TestJOCLed that set - the Xerces parser manually. This was to support early JDKs. The 1.3 - version of DBCP requires JDK 1.4+. - - - Added support for pooling managed connections. - - - Added BasicManagedDataSource, extending BasicDataSource. - Also improved extensibility of BasicDataSource by encapsulating - methods to create object pool, connection factory and datasource - instance previously embedded in createDataSource. - - - Changed behavior to allow Connection, Statement, PreparedStatement, - CallableStatement and ResultSet to be closed multiple times. The first - time close is called the resource is closed and any subsequent calls - have no effect. This behavior is required as per the Javadocs for these - classes. Also added tests for closing all types multiple times and - updated any tests that incorrectly assert that a resource can not be - closed more then once. Fixes DBCP-3, DBCP-5, DBCP-23 and DBCP-134. - - - Modified PoolingDataSource, PoolingDriver and DelegatingStatement to - assure that all returned Statements, PreparedStatements, - CallableStatements and ResultSets are wrapped with a delegating object, - which already properly handle the back pointers for Connection and - Statement. Also added tests to assure that the *same* object used - to create the statement or result set is returned from either - getConnection() or getStatement(). - - - SQLNestedException has been deprecated and will be replaced in DBCP 2.0 with - SQLException and standard Java exception chaining. - - - BasicDataSource.close() now permanently marks the data source as closed, - and no new connections can be obtained from the data source. At close all - idle connections are destroyed and the method returns. As the remaining - active connections are closed, they are destroyed. - - - Eliminated potential sources of NullPointerExceptions in - PoolingConnection. - - - Improved error recovery and listener cleanup in - KeyedCPDSConnectionFactory. Substituted calls to destroyObject with - _pool.invalidateObject on error to ensure pool active count is - decremented on error events. Ensured that events from closed or invalid - connections are ignored and listeners are cleaned up. - - - Fixed error in SharedPoolDataSource causing incorrect passwords to be - stored under certain conditions. - - - Added exception handler to ensure that PooledConnections are not - orphaned when an exception occurs in setUpDefaults or clearWarnings in - InstanceKeyDataSource.getConnection. - - - Made getPool synchronized in PoolableConnectionFactory. - Fixes inconsistent synchronization accessing _pool. - - - Fixed inconsistent synchronization on _rollbackAfterValidation, - _validationQuery and _pool in CPDSConnectionFactory and - KeyedCPDSConnectionFactory by making the first two volatile and making - both getter and setter for _pool synchronized. - - - - - See <a href="changes.html#a1.4">DBCP 1.4 Changes </a> for details. Version - 1.3 is identical to 1.4, other than JDBC 4 methods being filtered out of the DBCP 1.3 sources. Changes - Since 1.2.2 are the same for 1.3 and 1.4. - - - - - Add a <i>JNDI How To</i> to the User Guide. - - - DriverManagerConnectionFactory: blank user name and password handling. - - - Broken behaviour for BasicDataSource.setMaxActive(0). - - - BasicDataSource does not work with getConnection(String, String). - - - Enhancements to prepared statement in DriverAdapterCPDS. - - - Better messages and docs for LoginTimeout UnsupportedOperationException. - - - Error in JOCL snippet in org.apache.commons.dbcp package javadoc. - - - Added toString() methods to DelegatingPreparedStatement and DelegatingStatement - - - Changes to make DBCP compile on JDK 1.5 by adding source="1.4" to compiler - arguments (there are compiler errors in JDK 5.0 without this source switch - that cannot be fixed without JDK 5.0-specific syntax). - - - Per-user pooling with Oracle driver and default isolation settings. - - - Error in JOCL document in javadoc. - - - Added toString() method to DelegatingConnection. - - - Add DriverManager.invalidateConnection(). - - - Improved Exception nesting in ConnectionPool. - - - Fix broken website links for examples. - - - Modified PoolableConnection close method to invalidate instance - when invoked on an already closed connection. - - - Inserted null checks to avoid NPE in close operations. - - - Changed getReference method in InstanceKeyDataSource to return a - concrete factory and added implementations of getReference in concrete - subclasses. - - - Inserted null check in close method of SharedPoolDataSource to avoid - NPE when invoked on non-initialized pool. - - - Document fact that true values for testOnBorrow, testOnReturn, testWhileIdle - only have effect when validationQuery is set to a non-null string. - - - Modified activateObject in PoolableConnection to test connection - properties before resetting to defaults. - - - Corrected maxActive documentation in configuration.html. - - - Upgraded dependency to Pool 1.3. - - - Added connection info to SQLException messages when closed connections - (resp stmts) are accessed in DelegatingConnection, DelegatingStatement. - - - Fixed errors in pool parameter documentation and made - 0 value for _maxPreparedStatements in DriverAdapterCPDS behave - like a negative value, to be consistent with documentation - and pool behavior. - - - Made userKeys an instance variable (i.e., not static) - in SharedPoolDataSource. - - - Changed implementation of equals in - PoolingDataSource.PoolGuardConnectionWrapper - to ensure it is reflexive, even when wrapped connections are not - DelegatingConnections. - - - Added rollbackAfterValidation property and code to issue a rollback on a - connection after validation when this property is set to true to eliminate - Oracle driver exceptions. Default property value is false. - - - Removed dependency on Commons Collections by adding collections - 2.1 sources for LRUMap and SequencedHashMap with package scope to - datasources package. - - - Removed synchronization from prepareStatement methods in - PoolingConnection. Synchronization in these methods was causing - deadlocks. No resources other than the prepared statement pool are - accessed by these methods, and the pool methods are synchronized. - Also fixes DBCP-202. - - - - - - See <a href="release-notes-1.2.1.html">DBCP 1.2.1 Release Notes</a> for details. - - - - - - See <a href="release-notes-1.2.html">DBCP 1.2 Release Notes</a> for details. - - - - - - See <a href="release-notes-1.1.html">DBCP 1.1 Release Notes</a> for details. - - - - - - Initial Release - - - - - + + + + + + + + Apache Commons DBCP Release Notes + + + + + + Validation query not timing out on connections managed by SharedPoolDataSource. + Validation query not timing out on connections managed by PerUserPoolDataSource. + KeyedCPDSConnectionFactory.validateObject(UserPassKey, PooledObject) ignores timeouts less than 1 second when there is no validation query. + Modernize tests to use JUnit 5 features. + Javadoc is missing its Overview page. + + + Bump org.apache.commons:commons-parent from 78 to 79. + + Removed internal constructors and methods from the package-private class CPDSConnectionFactory; this is binary compatible. + Removed an internal constructor and methods from the package-private class KeyedCPDSConnectionFactory; this is binary compatible. + + + + Avoid object creation when invoking isDisconnectionSqlException #422. + PoolableConnectionFactory.destroyObject() method behaves incorrectly on ABANDONED connection, issue with unhandled AbstractMethodError. DelegatingConnection.abort(Executor) should delegate to Jdbc41Bridge + DelegatingConnection.setSchema(String) should delegate to Jdbc41Bridge. + Fix possible NullPointerException in PoolingConnection.close(). + PerUserPoolDataSource.registerPool() incorrectly replacing a CPDSConnectionFactory into managers map before throwing an IllegalStateException. + Fix PMD UnnecessaryFullyQualifiedName in AbandonedTrace. + Fix PMD UnnecessaryFullyQualifiedName in PoolableCallableStatement. + Fix PMD UnnecessaryFullyQualifiedName in PoolablePreparedStatement. + Fix PMD UnnecessaryFullyQualifiedName in Utils. + Fix PMD UnnecessaryFullyQualifiedName in LocalXAConnectionFactory. + Fix SpotBugs MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT in PerUserPoolDataSource. + Fix SpotBugs MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT in SharedPoolDataSource. + + Add support for ignoring non-fatal SQL state codes #421. + Add @FunctionalInterface to SwallowedExceptionListener. + Add missing Javadoc comments and descriptions. + Add tests, raise the bar for JaCoCo checks. + + Bump org.apache.commons:commons-parent from 66 to 78 #360, #371, #395, #420, #426, #436, #441, #449. + Bump commons-logging:commons-logging from 1.3.0 to 1.3.4 #368, #399, #423. + Bump org.apache.commons:commons-lang3 from 3.14.0 to 3.17.0 #404, #412, #427. + Bump org.hamcrest:hamcrest from 2.2 to 3.0 #410. + Bump org.slf4j:slf4j-simple from 2.0.13 to 2.0.16 #413, #418. + + + + BasicDataSource#setAbandonedUsageTracking has no effect. + PoolingConnection.toString() causes StackOverflowError. + PooledConnectionImpl.destroyObject(PStmtKey, PooledObject) can throw NullPointerException #312. + PoolingConnection.destroyObject(PStmtKey, PooledObject) can throw NullPointerException #312. + Fix examples in src/main/java/org/apache/commons/dbcp2/package-info.java. + + Add property project.build.outputTimestamp for build reproducibility. + Add null guards in DelegatingDatabaseMetaData constructor #352. + Data source bean creation failed due to mismatched return type of setter and getter for connectionInitSqls in BasicDataSource: Add BasicDataSource.setConnectionInitSqls(List). + + Use ReentrantLock in PoolableConnection.close, #591 + Bump commons-lang3 from 3.13.0 to 3.14.0. + Bump commons-parent from 64 to 66. + Bump org.slf4j:slf4j-simple from 2.0.9 to 2.0.12 #349. + + + + Update call sites of deprecated APIs from Apache Commons Pool. + + Add DataSourceMXBean.getUserName() and deprecate getUsername(). + + Bump h2 from 2.2.220 to 2.2.224, #308. + Bump commons-parent from 60 to 64. + Bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9 #301. + Bump org.apache.commons:commons-pool2 from 2.11.1 to 2.12.0. + Bump jakarta.transaction:jakarta.transaction-api from 1.3.1 to 1.3.3. + Bump commons-logging:commons-logging from 1.2 to 1.3.0. + + + + + Fix StackOverflowError in PoolableConnection.isDisconnectionSqlException #123. + + + PerUserPoolDataSourceFactory.getNewInstance(Reference) parsed defaultMaxWaitMillis as an int instead of a long. + + + Reimplement time tracking in AbandonedTrace with an Instant instead of a long. + + + Migrate away from deprecated APIs in Apache Commons Pool. + + + Fix possible NullPointerException in BasicDataSourceFactory.validatePropertyNames(). + + + Fix possible NullPointerException in BasicDataSourceFactory.getObjectInstance(). + + + Connection level JMX queries result in concurrent access to connection objects, causing errors #179. + + + UserPassKey should be Serializable. + + + LifetimeExceededException should extend SQLException. + + + Replace Exception with SQLException in some method signatures (preserves binary compatibility, not source). + + + Don't leak Connections when PoolableConnectionFactory.makeObject() fails to create a JMX ObjectName. + + + Performance: No need for map lookups if we traverse map entries instead of keys. + + + Performance: Refactor to use a static inner class in DataSourceXAConnectionFactory. + + + Reuse pattern of throwing XAException instead of NullPointerException in LocalXAConnectionFactory.LocalXAResource. + + + SpotBugs: An overridable method is called from constructors in PoolableCallableStatement. + + + SpotBugs: An overridable method is called from constructors in PoolablePreparedStatement. + + + Wrong property name logged in ConnectionFactoryFactory.createConnectionFactory(BasicDataSource, Driver). + + + Throw SQLException instead of NullPointerException when the connection is already closed. + + + CPDSConnectionFactory.makeObject() does not need to wrap and rethrow SQLException. + + + PoolingDataSource.close() now always throws SQLException. + + + [StepSecurity] ci: Harden GitHub Actions #282. + + + Fixes typos, missing or misplaced characters, and grammar issues #299. + + + + Add and use AbandonedTrace#setLastUsed(Instant). + + + Add and use Duration versions of now deprecated APIs that use ints and longs. + Internally track durations with Duration objects instead of ints and longs. + See the JApiCmp report for the complete list. + + + Add PMD check to default Maven goal. + + + Add Utils.getDisconnectionSqlCodes() and Utils.DISCONNECTION_SQL_CODES. + + + Make BasicDataSource.getConnectionPool() public. + + + Add github/codeql-action. + + + + Bump actions/cache from 2.1.6 to 3.0.8 #147, #176. + + + Bump actions/checkout from 2.3.4 to 3.0.2 #139, #143, #173. + + + Bump actions/setup-java from 2 to 3.6.0 #229. + + + Bump actions/upload-artifact from 3.1.0 to 3.1.1 #231. + + + Bump checkstyle from 8.44 to 9.3 #121, #130, #149, #158, #190. + + + Bump maven-checkstyle-plugin from 3.1.2 to 3.2.0 #210. + + + Bump commons-pool2 2.10.0 to 2.11.1. + + + Bump junit-jupiter from 5.8.0-M1 to 5.9.1 #125, #136, #157, #203, #218. + + + Bump spotbugs-maven-plugin from 4.3.0 to 4.7.3.0 #140, #154, #161, #178, #192, #200, #204, #213, #234. + + + Bump spotbugs from 4.3.0 to 4.7.3 #124, #133, #151, #164, #177, #189, #214, #230. + + + Bump org.mockito:mockito-core from 3.11.2 to 4.11.0, #128, #138, #152, #175, #188. #193, #208, #215, #232, #235, #246, #252. + + + Bump maven-javadoc-plugin from 3.3.0 to 3.4.1 #131, #184. + + + Bump maven-pmd-plugin from 3.14.0 to 3.19.0 #132, #172, #195. + + + Bump pmd from 6.44.0 to 6.52.0. + + + Bump narayana-jta from 5.12.0.Final to 5.12.7.Final #134, #156, #163, #185, #197. + + + Bump japicmp-maven-plugin from 0.15.3 to 0.17.1 #137, #166, #174, #211, #238. + + + Bump h2 from 1.4.200 to 2.2.220 #153, #183, #196, #287. + Update SQL for migration from H2 1.4.200 to 2.0.204 where "KEY" and "VALUE" are now reserved keywords. + + + Bump jboss-logging from 3.4.2.Final to 3.4.3.Final #162. + + + Bump slf4j-simple from 1.7.30 to 1.7.36 #169. + + + Bump commons-parent from 52 to 60 #180, #219, #254, #278. + + + Bump JaCoCo from 0.8.7 to 0.8.8. + + + Bump maven-surefire-plugin 2.22.2 to 3.0.0-M7. + + + Bump apache-rat-plugin 0.13 to 0.14. + + + Bump commons-lang3 from 3.12 to 3.13.0. + + + + + + Add and reuse Constants.KEY_USER and Constants.KEY_PASSWORD. + + + Add and reuse DataSourceMXBean. + + + Add and reuse DriverAdapterCPDS.{get|set}DurationBetweenEvictionRuns(), deprecate {get|set}TimeBetweenEvictionRunsMillis(long). + + + Add and reuse DriverAdapterCPDS.{get|set}MinEvictableIdleDuration(), deprecate {get|set}MinEvictableIdleTimeMillis(int). + + + Add and reuse CPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). + + + Add and reuse KeyedCPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). + + + Add and reuse KeyedCPDSConnectionFactory.setMaxConnLifetime(Duration), deprecate setMaxConnLifetimeMillis(long). + + + Add and reuse InstanceKeyDataSource.{get|set}DefaultMaxWait(Duration), deprecate {get|set}DefaultMaxWaitMillis(long). + + + + Fix test random failure on TestSynchronizationOrder.testInterposedSynchronization, #84. + + + ManagedConnection must clear its cached state after transaction completes, #75. + + + Minor Improvements #78. + + + Use abort rather than close to clean up abandoned connections. + + + Performance Enhancement: Call toArray with Zero Array Size #20. + + + Avoid exposing password via JMX #38. + + + Remove redundant initializers #98. + + + Simplify test assertions #100, #113. + + + DataSource implementations do not implement Wrapper interface correctly #93. + + + Replace FindBugs with SpotBugs. + + + DataSourceConnectionFactory.getUserPassword() may expose internal representation by returning DataSourceConnectionFactory.userPassword. + + + DataSourceXAConnectionFactory.getUserPassword() may expose internal representation by returning DataSourceXAConnectionFactory.userPassword. + + + DriverAdapterCPDS.getPasswordCharArray() may expose internal representation by returning DriverAdapterCPDS.userPassword. + + + new org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory(TransactionManager, XADataSource, String, char[], TransactionSynchronizationRegistry) may expose internal representation by storing an externally mutable object into DataSourceXAConnectionFactory.userPassword. + + + org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory.setPassword(char[]) may expose internal representation by storing an externally mutable object into DataSourceXAConnectionFactory.userPassword. + + + org.apache.commons.dbcp2.PStmtKey.getColumnIndexes() may expose internal representation by returning PStmtKey.columnIndexes. + + + org.apache.commons.dbcp2.PStmtKey.getColumnNames() may expose internal representation by returning PStmtKey.columnNames. + + + Use Collections.synchronizedList() Instead Of Vector #101. + + + Simplify and inline variables #99. + + + Update PoolKey#toString() to avoid revealing a user name is here. + + + Internal package private UserPassKey class stores its user name as a char[] as it already does the password. + + + Performance of DelegatingConnection.prepareStatement(String) regressed enormously in 2.8.0 compared to 1.4. + DelegatingConnection should also cache connection schema string to avoid calling the Connection#getSchema() for each key creation. + DelegatingConnection should also cache connection catalog string to avoid calling the Connection#getCatalog() for each key creation. + + + BasicDataSource should test for the presence of a security manager dynamically, not once on initialization. + + + + Bump mockito-core from 3.5.11 to 3.11.2 #66, #72, #77, #85, #91, #105, #110, #116. + + + Bump actions/checkout from v2.3.2 to v2.3.4 #65, #74. + + + Bump actions/cache from v2 to v2.1.6 #90, #108. + + + Bump commons-pool2 from 2.8.1 to 2.9.0. + + + Bump actions/setup-java from v1.4.2 to v2 #69. + + + Bump japicmp-maven-plugin from 0.14.3 to 0.15.2 #71, #82. + + + Bump maven-pmd-plugin from 3.13.0 to 3.14.0 #76. + + + Bump japicmp-maven-plugin from 0.14.4 to 0.15.3, #83. + + + Bump Hamcrest 1.3 -> 2.2 #70. + + + Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #88. + + + Bump junit-jupiter from 5.7.0 to 5.8.0-M1, #89, #106. + + + Bump narayana-jta from 5.10.6.Final to 5.12.0.Final #103, #111. + + + Bump maven-javadoc-plugin from 3.2.0 to 3.3.0 #107. + + + Bump commons.jacoco.version 0.8.6 -> 0.8.7. + + + Bump jboss-logging from 3.4.1.Final to 3.4.2.Final #109. + + + Bump org.jboss:jboss-transaction-spi from 7.6.0.Final to 7.6.1.Final. + + + Bump commons-pool2 from 2.9.0 to 2.10.0. + + + Bump checkstyle to 8.44. + + + Bump spotbugs from 4.2.3 to 4.3.0 #117. + + + Bump spotbugs-maven-plugin from 4.2.3 to 4.3.0 #118. + + + + + + Fix BasicManagedDataSource leak of connections opened after transaction is rollback-only #39. + + + Add clearStatementPoolOnReturn #42. + + + Add start, restart methods to BasicDataSource. #50. + + + + NPE when creating a SQLExceptionList with a null list. + + + Fix DelegatingConnection readOnly and autoCommit caching mechanism #35. + + + Fix regression introduced by unreleased code clean-up #63. + + + Update to PR#36 - PrepareStatement and prepareCall methods are extracted #37. + + + Do not display credentials in DriverAdapterCPDS.toString(). + + + Do not display credentials in DelegatingConnection.toString(). + + + Do not display credentials in DriverConnectionFactory.toString(). + + + Do not display credentials in PoolKey.toString(). + + + Do not display credentials in UserPassKey.toString(). + + + + Update Apache Commons Pool from 2.7.0 to 2.8.1, #48. + + + Update tests from H2 1.4.199 to 1.4.200. + + + Update tests from Mockito 3.0.0 to 3.5.11 #47, #60, #64. + + + Update tests from jboss-logging 3.4.0.Final to 3.4.1.Final. + + + Update tests from narayana-jta 5.9.5.Final to 5.10.6.Final, #61. + + + Update tests from junit-jupiter 5.5.1 to 5.7.0 #62. + + + Update tests from org.slf4j:slf4j-simple 1.7.26 to 1.7.30. + + + Update build from com.github.siom79.japicmp:japicmp-maven-plugin 0.13.1 to 0.14.3. + + + Update build from maven-javadoc-plugin 3.1.1 to 3.2.0. + + + Update build from maven-pmd-plugin 3.12.0 to 3.13.0. + + + Update org.apache.commons:commons-parent from 48 to 51. + + + Update jacoco-maven-plugin from 0.8.4 to 0.8.6. + + + Update maven-checkstyle-plugin from 3.0.0 to 3.1.1. + + + Update actions/checkout from v1 to v2.3.2, #44, #51. + + + Update actions/setup-java from v1.4.0 to v1.4.2 #58. + + + + + + ManagedDataSource#close() should declare used exceptions. + + + Add a ConnectionFactory class name setting for BasicDataSource.createConnectionFactory() #33. + + + Add missing Javadocs. + + + + Wrong JMX base name derived in BasicDataSource#updateJmxName. + + + Avoid NPE when calling DriverAdapterCPDS.toString(). + + + java.util.IllegalFormatException while building a message for a SQLFeatureNotSupportedException in Jdbc41Bridge.getObject(ResultSet,String,Class). + + + Fix Javadoc link in README.md #21. + + + + Close ObjectOutputStream before calling toByteArray() on underlying ByteArrayOutputStream #28. + + + Upgrade to JUnit Jupiter #19. + + + Fix tests on Java 11. + + + Update Apache Commons Pool from 2.6.1 to 2.6.2. + + + Add 'jmxName' property to web configuration parameters listing. + + + Update Apache Commons Pool from 2.6.2 to 2.7.0. + + + Make org.apache.commons.dbcp2.AbandonedTrace.removeTrace(AbandonedTrace) null-safe. + + + org.apache.commons.dbcp2.DelegatingStatement.close() should try to close ALL of its result sets even when an exception occurs. + + + org.apache.commons.dbcp2.DelegatingConnection.passivate() should close ALL of its resources even when an exception occurs. + + + org.apache.commons.dbcp2.PoolablePreparedStatement.passivate() should close ALL of its resources even when an exception occurs. + + + org.apache.commons.dbcp2.PoolableCallableStatement.passivate() should close ALL of its resources even when an exception occurs. + + + + Update tests from org.mockito:mockito-core 2.28.2 to 3.0.0. + + + Update tests from H2 1.4.198 to 1.4.199. + + + Update tests from com.h2database:h2 1.4.197 to 1.4.199. + + + Update tests from org.jboss.narayana.jta:narayana-jta 5.9.2.Final to 5.9.5.Final. + + + Update tests from org.jboss.logging:jboss-logging 3.3.2.Final to 3.4.0.Final. + + + Update tests from org.mockito:mockito-core 2.24.0 to 2.28.2. + + + Update tests from org.mockito:mockito-core 2.28.2 to 3.0.0. + + + + + Allow for manual connection eviction. + + + Allow DBCP to register with a TransactionSynchronizationRegistry for XA cases. + + + Make defensive copies of char[] passwords. + + + Do not try to register synchronization when the transaction is no longer active. + + + Do not double returnObject back to the pool if there is a transaction context with a shared connection. + + + Allow DBCP to work with old Java 6/JDBC drivers without throwing AbstractMethodError. + + + Add some toString() methods for debugging (never printing passwords.) + + + BasicManagedDataSource needs to pass the TSR with creating DataSourceXAConnectionFactory. + + + Add getters to some classes. + + + org.apache.commons.dbcp2.DriverManagerConnectionFactory should use a char[] instead of a String to store passwords. + + + Update Apache Commons Pool from 2.6.0 to 2.6.1. + + + + + Update Java requirement from version 7 to 8. + + + Support JDBC 4.2. + + + Support default schema in configuration. + + + Examines 'SQLException's thrown by underlying connections or statements for fatal (disconnection) errors. + + + Change default for fail-fast connections from false to true. + + + Prepared statement keys should take a Connection's schema into account. + + + Increase test coverage. + + + Update Apache Commons Pool from 2.5.0 to 2.6.0. + + + Avoid exceptions when closing a connection in mutli-threaded use case. + + + + + Connection leak during XATransaction in high load. + + + Drop Ant build. + + + Ensure DBCP ConnectionListener can deal with transaction managers which invoke rollback in a separate thread. + + + org.apache.commons.dbcp2.PStmtKey should make copies of given arrays in constructors. + + + Remove duplicate code in org.apache.commons.dbcp2.cpdsadapter.PStmtKeyCPDS. + + + Add support for pooling CallableStatements to the org.apache.commons.dbcp2.cpdsadapter package. + + + Deprecate use of PStmtKeyCPDS in favor of PStmtKey. + + + org.apache.commons.dbcp2.DataSourceConnectionFactory should use a char[] instead of a String to store passwords. + + + org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory should use a char[] instead of a String to store passwords. + + + org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS should use a char[] instead of a String to store passwords. + + + org.apache.commons.dbcp2.datasources.CPDSConnectionFactory should use a char[] instead of a String to store passwords. + + + org.apache.commons.dbcp2.datasources internals should use a char[] instead of a String to store passwords. + + + org.apache.commons.dbcp2.datasources.InstanceKeyDataSourceFactory.closeAll() does not close all. + + + + + AbandonedTrace.getTrace() contains race condition. + + + Avoid javax.management.InstanceNotFoundException on shutdown when a bean is not registered. Closes #9. + + + Make constant public: org.apache.commons.dbcp2.PoolingDriver.URL_PREFIX. + + + DriverAdapterCPDS.setUser(), setPassword(), and getPooledConnection() with null arguments throw NullPointerExceptions when connection properties are set. + + + Add API org.apache.commons.dbcp2.datasources.PerUserPoolDataSource.clear(). + + + NPE for org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS.setConnectionProperties(null). + + + The method org.apache.commons.dbcp2.PoolingDriver.getConnectionPool(String) does not tell you which pool name is not registered when it throws an exception. + + + + + Update Apache Commons Pool from 2.4.2 to 2.5.0. + + + OSGi declarations contain multiple import headers for javax.transaction. + + + Wrong parameter name in site documentation for BasicDataSource Configuration Parameters. + + + Add jmxName to properties set by BasicDataSourceFactory. This + enables container-managed pools created from JNDI Resource + definitions to enable JMX by supplying a valid root JMX name. + + + NullPointerException thrown when calling ManagedConnection.isClosed(). + + + InvalidateConnection can result in closed connection returned by getConnection. + + + Complete the fix for DBCP-418, enabling PoolableConnection class to load in environments + (such as GAE) where the JMX ManagementFactory is not available. + + + Add constructor DriverManagerConnectionFactory(String). + + + Ensure that the cacheState setting is used when statement pooling is + disabled. + + + Ensure that setSoftMinEvictableIdleTimeMillis is used when working with + BasicDataSource. + + + Correct the name of the configuration attribute + softMinEvictableIdleTimeMillis. + + + Avoid potential infinite loops when checking if an SQLException is fatal + for a connection or not. + + + Expand the fail-fast for fatal connection errors feature to include + managed connections. + + + Correct a typo in the method name + PoolableConnectionFactory#setMaxOpenPreparedStatements. The old method + remains but is deprecated so not to break clients currently using the + incorrect name. + + + Refactoring to prepare for a future patch to enable pooling of all + prepared and callable statements in PoolingConnection. + + + Ensure that a thread's interrupt status is visible to the caller if the + thread is interrupted during a call to + PoolingDataSource.getConnection(). + + + Make it simpler to extend BasicDataSource to allow sub-classes to + provide custom GenericObjectPool implementations. + + + When using a BasicDataSource, pass changes related to the handling of + abandoned connections to the underlying pool so that the pool + configuration may be updated dynamically. + + + Enable pooling of all prepared and callable statements + inPoolingConnection. + + + + + Updated pool version to 2.4.2. The fix for POOL-300 may cause DBCP + users to see more reports of abandoned connections (if removal and logging + are configured). Prior to the fix for POOL-300, the PrintWriter used to log + abandoned connection stack traces was not being flushed on each log event. + + + Added BasicDataSource abandonedUsageTracking property missing from BasicDataSourceFactory. + + + SharedPoolDataSource getConnection fails when testOnBorrow is set with + a null validation query. + + + Nested connections in a transaction (local) throws null pointer. + + + BasicDataSource does not set disconnectionSql properties on its PoolableConnectionFactory. + + + + + InstanceKeyDataSource discards native SQLException when given password does not match + password used to create the connection. + + + Update Apache Commons Logging to 1.2 from 1.1.3. + + + Correct some Javadoc references to Apache Commons Pool 2 classes that + have changed names since Pool 1.x. + + + Do not ignore the configured custom eviction policy when creating a + BasicDataSource. + + + Added invalidateConnection method to BasicDataSource. + + + Unsuccessful Connection enlistment in XA Transaction ignored by TransactionContext. + + + Made expired connection logging configurable in BasicDataSource. Setting + logExpiredConnections to false suppresses expired connection log messages. + + + Made Datasources implement AutoCloseable. + + + Added fastFailValidation property to PoolableConnection, configurable in + BasicDataSource. When set to true, connections that have previously thrown + fatal disconnection errors will fail validation immediately (no driver calls). + + + Changed BasicDataSource createDataSource method to ensure that initialization + completes before clients get reference to newly created instances. + + + Fixed connection leak when SQLException is thrown while enlisting an XA + transaction. + + + Setting jmxName to null should suppress JMX registration of connection + and statement pools. + + + Eliminated synchronization in BasicDataSource getNumActive, getNumIdle methods. + + + Added property name verification to BasicDataSourceFactory. References including + obsolete or unrecognized properties now generate log messages. + + + + + Small performance improvements when returning connections to the pool. + + + Fixed DelegatingStatement close to ensure closed statements do not retain references + to pooled prepared statements. Due to finalization code added in 2.0, this was causing + pooled prepared statements to be closed by GC while in use by clients. + + + Added check in PoolingDataSource constructor to ensure that the connection factory + and pool are properly linked. + + + Fixed connection leak when managed connections are closed during transactions. + + + Enable PoolableConnection class to load without JMX. + + + + + BasicManagedDataSource - unregister from JMX on close(). + + + Log validation failures of poolable connections. + + + DelegatingStatement.close() fails with NPE if statement is null + + + CPDSConnectionFactory.validateObject(Object) ignores Throwable. + + + Provide a new option (cacheState) to cache current values of autoCommit + and readOnly so database queries are not required for every call to the + associated getters. This option is enabled by default. + + + Removed unnecessary synchronisation in BasicDataSource#createDataSource. + + + The Java package name has been changed from org.apache.commons.dbcp to + org.apache.commons.dbcp2. + + + Update to Commons Pool 2 (based on java.util.concurrent) to provide + pooling functionality. + + + Updated source code for Java 1.6 (added @Override & @Deprecated + annotations). + + + Removed JOCL support. + + + Remove deprecated SQLNestedException. + + + Fix threading issues with accessToUnderlyingConnectionAllowed attribute + of PoolingDriver which is used to support unit testing. + + + BasicDataSource instances are now exposed via JMX. All the configuration + properties are available as is the connection pool and the statement + pools (if statement pooling is enabled). + + + Fix thread safety issues in the SharedPoolDataSource and the + PerUserPoolDataSource. + + + Allow accessToUnderlyingConnectionAllowed to be configured when + configuration takes place via JNDI in a JavaEE container. + + + Fix threading issue when using multiple instances of the + SharedPoolDataSource concurrently. + + + Ensure that the close state of a pooled connection and the underlying + connection is consistent when the underlying connection is closed as a + result of an error condition. + + + Make all mutable fields private. + + + Return BasicDataSource rather than DataSource from + BasicDataSourceFactory so a cast is not required to use BasicDataSource + specific methods. + + + The equals() implementations of the DelegatingXxx classes are now + symmetric. There are some important API changes underlying this fix. + Firstly, two DelegatingXxx instances are no longer considered equal if + they have the same innermost delegate. Secondly, a DelegatingXxx + instance is not considered equal to its innermost delegate. The + getInnermostDelegateInternal() method has been made public (but remains + part of the internal API) to allow classes extending this implementation + to access the innermost delegate when required. + + + Expose the new Pool 2 property evictionPolicyClassName to enable more + sophisticated eviction strategies to be used. + + + Add support for pooling PreparedStatements that use auto-generated keys. + + + Enable JDBC resources that are no longer referenced by client code to be + eligible for garbage collection. + + + Enable DBCP to work with a SecurityManager such that only DBCP needs to + be granted the necessary permissions to communicate with the database. + + + Correct path to Javadoc overview in build.xml. + + + The default values for readOnly, autoCommit and transactionIsolation are + now taken from the JDBC driver. No calls to setReadOnly(), + setAutoCommit() or setTransactionIsolation() will be made for a newly + borrowed connection unless a default is explicitly configured and the + connection is currently using a different setting. + + + Register pooled connections with JMX so that they may be forcibly closed + via JMX if required. + + + Modify SharedPoolDataSource and PerUserPoolDataSource to expose all of + the configuration properties of the underlying connection pool(s). This + represents a significant refactoring of these classes and a number of + property names have changed as a result. + + + Provide an option to control if autoCommit is always set to true when a + connection is returned to the connection pool. + + + Provide an option to control if rollback is called when a connection is + returned to the poll with autoCommit disabled. + + + Provide an option to set the default query timeout. + + + Connection.isValid() should not throw an SQLException if the connection + is closed. + + + Use Connection.isValid() to validate connections unless a validation + query is explicitly configured. Note that this means it is no longer + necessary for a validation query to be specified in order for validation + to take place. When testing with Oracle, this resulted in database + validation being approximately 7 times faster. + + + Add support for validation testing database connections on creation. + + + + + Correct the documentation for the maxOpenPreparedStatements parameter + and review the use of the phrase non-positive throughout the + documentation and javadoc, replacing it with negative where that is the + correct definition to use. + + + Avoid multiple calls to Connection.getAutoCommit() in + PoolableConnectionFactory.passivateObject() as it could be an expensive + call. + + + Use one line per statement for methods with multiple statements rather + than using a single line. + + + Expose all of the AbandonedConfig properties through a BasicDataSource. + + + Correct implementation of DelegatingConnection.isWrapperFor() so it + works correctly with older JDBC drivers. + + + Correct implementation of DelegatingStatement.isWrapperFor(). Also fix + DelegatingDatabaseMetaData.isWrapperFor() and + DelegatingResultSet.isWrapperFor() that had the same problem. + + + LocalXAConnectionFactory does not properly check if Xid is equal to + currentXid when resuming which may result in an XAException. + + + Ensure that the XAConnection is closed when the associated Connection is + closed. + + + Clarify Jaavdoc for isClosed() method of PoolableConnection. + + + Avoid NullPointerException and throw an XAException if an attempt is + made to commit the current transaction for a connection when no + transaction has been started. + + + Using batchUpdate() should not invalidate the PreparedStatement when it + is returned to the pool. + + + Improve documentation for JNDI example using BasicDataSource. + + + + + Exposed GenericObjectPool's softMinEvictableIdleTimeMillis property for + configuration and use by BasicDataSource. + + + Made equals reflexive in DelegatingStatement (and subclasses), DelegatingMetaData, + DelegatingResultSet and PoolingDriver#PoolGuardConnectionWrapper. + + + Modified createDataSource method in BasicDataSource to ensure that GenericObjectPool + Evictor tasks are not started and orphaned when BasicDataSource encounters errors on + initialization. Prior to this fix, when minIdle and timeBetweenEvictionRunsMillis + are both positive, Evictors orphaned by failed initialization can continue to + generate database connection requests. This issue is duplicated by DBCP-339 + and DBCP-93. + + + Changed DelegatingDatabaseMetaData to no longer add itself to the AbandonedTrace + of its parent connection. This was causing excessive memory consumption and was + not necessary, as resultsets created by DelegatingDatabaseMetaData instances are + attached to the parent connection's trace on creation. Also fixes DBCP-352. + + + Modified execute methods of Statement objects to ensure that whenever + a statement is used, the lastUsed property of its parent connection is + updated. + + + Correctly implemented the option to configure the class loader used to load + the JDBC driver. + + + LIFO configuration option has been added to BasicDataSource. When set + to true (the default), the pool acts as a LIFO queue; setting to false + causes connections to enter and exit to pool in FIFO order. + + + Test transitive dependencies brought in by geronimo-transaction created + version conflicts (commons logging and junit). These have been explicitly + excluded in the POM. + + + BasicDataSourceFactory incorrectly used "initConnectSqls" in versions + 1.3 and 1.4 of DBCP as the property name for connectionInitSqls. + Online docs for 1.3/1/4 have been updated to reflect this inconsistency. + The BasicDataSourceFactory property name has been changed to "connectInitSqls" + to match the online docs and the BasicDataSource property name. + + + + + Eliminated poolKeys cache from PerUserPoolDataSource. + + + Eliminated userKeys LRUMap cache from SharedPoolDataSource. + + + Made private fields final where possible. + + + PerUserPoolDataSource.getPooledConnectionAndInfo multi-threading bug. + + + Remove throws clause from method that does not throw an exception. + + + Remove code that catches and ignores Exceptions when calling + PooledConnection.removeConnectionEventListener(ConnectionEventListener) + as the method does not throw any Exceptions. + + + Remove impossible null check. + + + Renamed variables with duplicate names in different scopes. + + + Clarified javadoc for BasicDataSource close() method. + + + Made PoolingConnection pool CallableStatements. When BasicDataSource's + poolPreparedStatements property is true, CallableStatements are now + pooled along with PreparedStatements. The maxOpenPreparedStatements + property limits the combined number of Callable and Prepared statements + that can be in use at a given time. + + + Use an API specific exception for logging abandoned objects to make + scanning the logs for these exceptions simpler and to provide a better + message that includes the creation time of the abandoned object. + + + Ensure Statement.getGeneratedKeys() works correctly with the CPDS + adapter. + + + Removed incorrectly advertised ClassNotFoundException from + JOCLContentHandler.ConstructorDetails.createObject(). + + + Make the class loader used to load the JDBC driver configurable for the + BasicDatasource. + + + Handle user password changes for InstanceKeyDataSources. + + + Made XADataSource configurable in BasicManagedDataSource. + + + Added PoolableManagedConnection and PoolableManagedConnectionFactory so that + pooled managed connections can unregister themselves from transaction registries, + avoiding resource leaks. + + + Added connectionProperties property to DriverAdapterCPDS. + + + Added a validationQueryTimeout configuration parameter to BasicDataSource + allowing the user to specify a timeout value (in seconds) for connection + validation queries. + + + Added a connectionInitSqls configuration parameter to BasicDataSource + allowing the user to specify a collection of SQL statements to execute + one time when a physical database connection is first opened. + + + PoolableConnectionFactory.makeObject() is no longer synchronized. This + provides improved response times when load spikes at the cost of a + faster rise in database server load. This change was made as a partial + fix for DBCP-212. The synchronization changes in Commons Pool 1.5 complete + the fix for this issue. + + + Reverted DelegatingConnection close to 1.2.2 version to ensure + open statements are closed before the underlying connection is closed. + + + Refactor DelegatingConnection and ManagedConnection enable overridden + equals() and hashcode() to work correctly. + + + Add a DelegatingDatabaseMetaData to track ResultSets returned from + DatabaseMetaData objects. + + + Modified BasicDataSourceFactory to complete initialization of the pool + by creating initialSize connections rather than leaving this to lazy + initialization when the pool is used. + + + Eliminated masked _stmt field in descendents of DelegatingStatement. + + + Modified DBCP sources to support compilation under JDK 1.4-1.6 + using Ant flags to do conditional compilation. + + + Added a static initializer to BasicDatasource that calls + DriverManager.getDrivers() to force initialization before we ever do + anything that might use Class.forName() to load (and register) a JDBC + driver. + + + Eliminated direct System.out calls in AbandonedTrace. + + + Modified DelegatingStatement close to clear open batches. + + + Eliminated unused private "parent" field in AbandonedTrace. + + + Fixed errors handling boolean-valued Reference properties in + InstanceKeyObjectFactory, DriverAdapterCPDS that were causing + testOnBorrow and poolPreparedStatements properties to be incorrectly + set when creating objects from javax.naming.Reference instances. + + + Made private instance fields of AbandonedTrace volatile (parent, + createdBy, lastUsed, createdTime) or final (trace). + + + Narrowed synchronization in AbandonedTrace to resolve an Evictor deadlock. + + + Corrected Javadoc to state that getLoginTimeout and setLoginTimeout are + NOT supported by BasicDataSource. + + + Added Maven 2 pom.xml. Removed a block of code from TestJOCLed that set + the Xerces parser manually. This was to support early JDKs. The 1.3 + version of DBCP requires JDK 1.4+. + + + Added support for pooling managed connections. + + + Added BasicManagedDataSource, extending BasicDataSource. + Also improved extensibility of BasicDataSource by encapsulating + methods to create object pool, connection factory and datasource + instance previously embedded in createDataSource. + + + Changed behavior to allow Connection, Statement, PreparedStatement, + CallableStatement and ResultSet to be closed multiple times. The first + time close is called the resource is closed and any subsequent calls + have no effect. This behavior is required as per the Javadocs for these + classes. Also added tests for closing all types multiple times and + updated any tests that incorrectly assert that a resource can not be + closed more then once. Fixes DBCP-3, DBCP-5, DBCP-23 and DBCP-134. + + + Modified PoolingDataSource, PoolingDriver and DelegatingStatement to + assure that all returned Statements, PreparedStatements, + CallableStatements and ResultSets are wrapped with a delegating object, + which already properly handle the back pointers for Connection and + Statement. Also added tests to assure that the *same* object used + to create the statement or result set is returned from either + getConnection() or getStatement(). + + + SQLNestedException has been deprecated and will be replaced in DBCP 2.0 with + SQLException and standard Java exception chaining. + + + BasicDataSource.close() now permanently marks the data source as closed, + and no new connections can be obtained from the data source. At close all + idle connections are destroyed and the method returns. As the remaining + active connections are closed, they are destroyed. + + + Eliminated potential sources of NullPointerExceptions in + PoolingConnection. + + + Improved error recovery and listener cleanup in + KeyedCPDSConnectionFactory. Substituted calls to destroyObject with + _pool.invalidateObject on error to ensure pool active count is + decremented on error events. Ensured that events from closed or invalid + connections are ignored and listeners are cleaned up. + + + Fixed error in SharedPoolDataSource causing incorrect passwords to be + stored under certain conditions. + + + Added exception handler to ensure that PooledConnections are not + orphaned when an exception occurs in setUpDefaults or clearWarnings in + InstanceKeyDataSource.getConnection. + + + Made getPool synchronized in PoolableConnectionFactory. + Fixes inconsistent synchronization accessing _pool. + + + Fixed inconsistent synchronization on _rollbackAfterValidation, + _validationQuery and _pool in CPDSConnectionFactory and + KeyedCPDSConnectionFactory by making the first two volatile and making + both getter and setter for _pool synchronized. + + + + + See <a href="changes.html#a1.4">DBCP 1.4 Changes </a> for details. Version + 1.3 is identical to 1.4, other than JDBC 4 methods being filtered out of the DBCP 1.3 sources. Changes + Since 1.2.2 are the same for 1.3 and 1.4. + + + + + Add a <i>JNDI How To</i> to the User Guide. + + + DriverManagerConnectionFactory: blank user name and password handling. + + + Broken behaviour for BasicDataSource.setMaxActive(0). + + + BasicDataSource does not work with getConnection(String, String). + + + Enhancements to prepared statement in DriverAdapterCPDS. + + + Better messages and docs for LoginTimeout UnsupportedOperationException. + + + Error in JOCL snippet in org.apache.commons.dbcp package javadoc. + + + Added toString() methods to DelegatingPreparedStatement and DelegatingStatement + + + Changes to make DBCP compile on JDK 1.5 by adding source="1.4" to compiler + arguments (there are compiler errors in JDK 5.0 without this source switch + that cannot be fixed without JDK 5.0-specific syntax). + + + Per-user pooling with Oracle driver and default isolation settings. + + + Error in JOCL document in javadoc. + + + Added toString() method to DelegatingConnection. + + + Add DriverManager.invalidateConnection(). + + + Improved Exception nesting in ConnectionPool. + + + Fix broken website links for examples. + + + Modified PoolableConnection close method to invalidate instance + when invoked on an already closed connection. + + + Inserted null checks to avoid NPE in close operations. + + + Changed getReference method in InstanceKeyDataSource to return a + concrete factory and added implementations of getReference in concrete + subclasses. + + + Inserted null check in close method of SharedPoolDataSource to avoid + NPE when invoked on non-initialized pool. + + + Document fact that true values for testOnBorrow, testOnReturn, testWhileIdle + only have effect when validationQuery is set to a non-null string. + + + Modified activateObject in PoolableConnection to test connection + properties before resetting to defaults. + + + Corrected maxActive documentation in configuration.html. + + + Upgraded dependency to Pool 1.3. + + + Added connection info to SQLException messages when closed connections + (resp stmts) are accessed in DelegatingConnection, DelegatingStatement. + + + Fixed errors in pool parameter documentation and made + 0 value for _maxPreparedStatements in DriverAdapterCPDS behave + like a negative value, to be consistent with documentation + and pool behavior. + + + Made userKeys an instance variable (i.e., not static) + in SharedPoolDataSource. + + + Changed implementation of equals in + PoolingDataSource.PoolGuardConnectionWrapper + to ensure it is reflexive, even when wrapped connections are not + DelegatingConnections. + + + Added rollbackAfterValidation property and code to issue a rollback on a + connection after validation when this property is set to true to eliminate + Oracle driver exceptions. Default property value is false. + + + Removed dependency on Commons Collections by adding collections + 2.1 sources for LRUMap and SequencedHashMap with package scope to + datasources package. + + + Removed synchronization from prepareStatement methods in + PoolingConnection. Synchronization in these methods was causing + deadlocks. No resources other than the prepared statement pool are + accessed by these methods, and the pool methods are synchronized. + Also fixes DBCP-202. + + + + + + See <a href="release-notes-1.2.1.html">DBCP 1.2.1 Release Notes</a> for details. + + + + + + See <a href="release-notes-1.2.html">DBCP 1.2 Release Notes</a> for details. + + + + + + See <a href="release-notes-1.1.html">DBCP 1.1 Release Notes</a> for details. + + + + + + Initial Release + + + + + diff --git a/src/main/java/org/apache/commons/dbcp2/AbandonedTrace.java b/src/main/java/org/apache/commons/dbcp2/AbandonedTrace.java index f84bdd95f0..71999a0b8b 100644 --- a/src/main/java/org/apache/commons/dbcp2/AbandonedTrace.java +++ b/src/main/java/org/apache/commons/dbcp2/AbandonedTrace.java @@ -1,228 +1,228 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.lang.ref.WeakReference; -import java.sql.SQLException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.function.Consumer; - -import org.apache.commons.pool2.TrackedUse; - -/** - * Tracks connection usage for recovering and reporting abandoned connections. - *

- * The JDBC Connection, Statement, and ResultSet classes extend this class. - *

- * - * @since 2.0 - */ -public class AbandonedTrace implements TrackedUse, AutoCloseable { - - static void add(final AbandonedTrace receiver, final AbandonedTrace trace) { - if (receiver != null) { - receiver.addTrace(trace); - } - } - - /** A list of objects created by children of this object. */ - private final List> traceList = new ArrayList<>(); - - /** Last time this connection was used. */ - private volatile Instant lastUsedInstant = Instant.EPOCH; - - /** - * Creates a new AbandonedTrace without config and without doing abandoned tracing. - */ - public AbandonedTrace() { - init(null); - } - - /** - * Constructs a new AbandonedTrace with a parent object. - * - * @param parent - * AbandonedTrace parent object. - */ - public AbandonedTrace(final AbandonedTrace parent) { - init(parent); - } - - /** - * Adds an object to the list of objects being traced. - * - * @param trace - * AbandonedTrace object to add. - */ - protected void addTrace(final AbandonedTrace trace) { - synchronized (this.traceList) { - this.traceList.add(new WeakReference<>(trace)); - } - setLastUsed(); - } - - /** - * Clears the list of objects being traced by this object. - */ - protected void clearTrace() { - synchronized (this.traceList) { - this.traceList.clear(); - } - } - - /** - * Subclasses can implement this nop. - * - * @throws SQLException Ignored here, for subclasses. - * @since 2.10.0 - */ - @Override - public void close() throws SQLException { - // nop - } - - /** - * Closes this resource and if an exception is caught, then calls {@code exceptionHandler}. - * - * @param exceptionHandler Consumes exception thrown closing this resource. - * @since 2.10.0 - */ - protected void close(final Consumer exceptionHandler) { - Utils.close(this, exceptionHandler); - } - - /** - * Gets the last time this object was used in milliseconds. - * - * @return long time in milliseconds. - */ - @Override - @Deprecated - public long getLastUsed() { - return lastUsedInstant.toEpochMilli(); - } - - @Override - public Instant getLastUsedInstant() { - return lastUsedInstant; - } - - /** - * Gets a list of objects being traced by this object. - * - * @return List of objects. - */ - protected List getTrace() { - final int size = traceList.size(); - if (size == 0) { - return Collections.emptyList(); - } - final ArrayList result = new ArrayList<>(size); - synchronized (this.traceList) { - final Iterator> iter = traceList.iterator(); - while (iter.hasNext()) { - final AbandonedTrace trace = iter.next().get(); - if (trace == null) { - // Clean-up since we are here anyway - iter.remove(); - } else { - result.add(trace); - } - } - } - return result; - } - - /** - * Initializes abandoned tracing for this object. - * - * @param parent - * AbandonedTrace parent object. - */ - private void init(final AbandonedTrace parent) { - add(parent, this); - } - - /** - * Removes this object the source object is tracing. - * - * @param source The object tracing - * @since 2.7.0 - */ - protected void removeThisTrace(final Object source) { - if (source instanceof AbandonedTrace) { - AbandonedTrace.class.cast(source).removeTrace(this); - } - } - - /** - * Removes a child object this object is tracing. - * - * @param trace - * AbandonedTrace object to remove. - */ - protected void removeTrace(final AbandonedTrace trace) { - synchronized (this.traceList) { - final Iterator> iter = traceList.iterator(); - while (iter.hasNext()) { - final AbandonedTrace traceInList = iter.next().get(); - if (trace != null && trace.equals(traceInList)) { - iter.remove(); - break; - } - if (traceInList == null) { - // Clean-up since we are here anyway - iter.remove(); - } - } - } - } - - /** - * Sets the time this object was last used to the current time in milliseconds. - */ - protected void setLastUsed() { - lastUsedInstant = Instant.now(); - } - - /** - * Sets the instant this object was last used. - * - * @param lastUsedInstant - * instant. - * @since 2.10.0 - */ - protected void setLastUsed(final Instant lastUsedInstant) { - this.lastUsedInstant = lastUsedInstant; - } - - /** - * Sets the time in milliseconds this object was last used. - * - * @param lastUsedMillis - * time in milliseconds. - * @deprecated Use {@link #setLastUsed(Instant)} - */ - @Deprecated - protected void setLastUsed(final long lastUsedMillis) { - this.lastUsedInstant = Instant.ofEpochMilli(lastUsedMillis); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.lang.ref.WeakReference; +import java.sql.SQLException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.pool2.TrackedUse; + +/** + * Tracks connection usage for recovering and reporting abandoned connections. + *

+ * The JDBC Connection, Statement, and ResultSet classes extend this class. + *

+ * + * @since 2.0 + */ +public class AbandonedTrace implements TrackedUse, AutoCloseable { + + static void add(final AbandonedTrace receiver, final AbandonedTrace trace) { + if (receiver != null) { + receiver.addTrace(trace); + } + } + + /** A list of objects created by children of this object. */ + private final List> traceList = new ArrayList<>(); + + /** Last time this connection was used. */ + private volatile Instant lastUsedInstant = Instant.EPOCH; + + /** + * Creates a new AbandonedTrace without config and without doing abandoned tracing. + */ + public AbandonedTrace() { + init(null); + } + + /** + * Constructs a new AbandonedTrace with a parent object. + * + * @param parent + * AbandonedTrace parent object. + */ + public AbandonedTrace(final AbandonedTrace parent) { + init(parent); + } + + /** + * Adds an object to the list of objects being traced. + * + * @param trace + * AbandonedTrace object to add. + */ + protected void addTrace(final AbandonedTrace trace) { + synchronized (this.traceList) { + this.traceList.add(new WeakReference<>(trace)); + } + setLastUsed(); + } + + /** + * Clears the list of objects being traced by this object. + */ + protected void clearTrace() { + synchronized (this.traceList) { + this.traceList.clear(); + } + } + + /** + * Subclasses can implement this nop. + * + * @throws SQLException Ignored here, for subclasses. + * @since 2.10.0 + */ + @Override + public void close() throws SQLException { + // nop + } + + /** + * Closes this resource and if an exception is caught, then calls {@code exceptionHandler}. + * + * @param exceptionHandler Consumes exception thrown closing this resource. + * @since 2.10.0 + */ + protected void close(final Consumer exceptionHandler) { + Utils.close(this, exceptionHandler); + } + + /** + * Gets the last time this object was used in milliseconds. + * + * @return long time in milliseconds. + */ + @Override + @Deprecated + public long getLastUsed() { + return lastUsedInstant.toEpochMilli(); + } + + @Override + public Instant getLastUsedInstant() { + return lastUsedInstant; + } + + /** + * Gets a list of objects being traced by this object. + * + * @return List of objects. + */ + protected List getTrace() { + final int size = traceList.size(); + if (size == 0) { + return Collections.emptyList(); + } + final ArrayList result = new ArrayList<>(size); + synchronized (this.traceList) { + final Iterator> iter = traceList.iterator(); + while (iter.hasNext()) { + final AbandonedTrace trace = iter.next().get(); + if (trace == null) { + // Clean-up since we are here anyway + iter.remove(); + } else { + result.add(trace); + } + } + } + return result; + } + + /** + * Initializes abandoned tracing for this object. + * + * @param parent + * AbandonedTrace parent object. + */ + private void init(final AbandonedTrace parent) { + add(parent, this); + } + + /** + * Removes this object the source object is tracing. + * + * @param source The object tracing + * @since 2.7.0 + */ + protected void removeThisTrace(final Object source) { + if (source instanceof AbandonedTrace) { + AbandonedTrace.class.cast(source).removeTrace(this); + } + } + + /** + * Removes a child object this object is tracing. + * + * @param trace + * AbandonedTrace object to remove. + */ + protected void removeTrace(final AbandonedTrace trace) { + synchronized (this.traceList) { + final Iterator> iter = traceList.iterator(); + while (iter.hasNext()) { + final AbandonedTrace traceInList = iter.next().get(); + if (trace != null && trace.equals(traceInList)) { + iter.remove(); + break; + } + if (traceInList == null) { + // Clean-up since we are here anyway + iter.remove(); + } + } + } + } + + /** + * Sets the time this object was last used to the current time in milliseconds. + */ + protected void setLastUsed() { + lastUsedInstant = Instant.now(); + } + + /** + * Sets the instant this object was last used. + * + * @param lastUsedInstant + * instant. + * @since 2.10.0 + */ + protected void setLastUsed(final Instant lastUsedInstant) { + this.lastUsedInstant = lastUsedInstant; + } + + /** + * Sets the time in milliseconds this object was last used. + * + * @param lastUsedMillis + * time in milliseconds. + * @deprecated Use {@link #setLastUsed(Instant)} + */ + @Deprecated + protected void setLastUsed(final long lastUsedMillis) { + this.lastUsedInstant = Instant.ofEpochMilli(lastUsedMillis); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java index e38ee67e9d..f00b1a2b12 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java @@ -1,2673 +1,2673 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.time.Duration; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Properties; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.logging.Logger; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.management.MBeanRegistration; -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.NotCompliantMBeanException; -import javax.management.ObjectName; -import javax.management.StandardMBean; -import javax.sql.DataSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.AbandonedConfig; -import org.apache.commons.pool2.impl.BaseObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - -/** - * Basic implementation of {@code javax.sql.DataSource} that is configured via JavaBeans properties. - *

- * This is not the only way to combine the commons-dbcp2 and commons-pool2 packages, but provides a - * one-stop solution for basic requirements. - *

- * - * @since 2.0 - */ -public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBeanRegistration, AutoCloseable { - - private static final Log log = LogFactory.getLog(BasicDataSource.class); - - static { - // Attempt to prevent deadlocks - see DBCP - 272 - DriverManager.getDrivers(); - try { - // Load classes now to prevent AccessControlExceptions later - // A number of classes are loaded when getConnection() is called - // but the following classes are not loaded and therefore require - // explicit loading. - if (Utils.isSecurityEnabled()) { - final ClassLoader loader = BasicDataSource.class.getClassLoader(); - final String dbcpPackageName = BasicDataSource.class.getPackage().getName(); - loader.loadClass(dbcpPackageName + ".DelegatingCallableStatement"); - loader.loadClass(dbcpPackageName + ".DelegatingDatabaseMetaData"); - loader.loadClass(dbcpPackageName + ".DelegatingPreparedStatement"); - loader.loadClass(dbcpPackageName + ".DelegatingResultSet"); - loader.loadClass(dbcpPackageName + ".PoolableCallableStatement"); - loader.loadClass(dbcpPackageName + ".PoolablePreparedStatement"); - loader.loadClass(dbcpPackageName + ".PoolingConnection$StatementType"); - loader.loadClass(dbcpPackageName + ".PStmtKey"); - - final String poolPackageName = PooledObject.class.getPackage().getName(); - loader.loadClass(poolPackageName + ".impl.LinkedBlockingDeque$Node"); - loader.loadClass(poolPackageName + ".impl.GenericKeyedObjectPool$ObjectDeque"); - } - } catch (final ClassNotFoundException cnfe) { - throw new IllegalStateException("Unable to pre-load classes", cnfe); - } - } - - /** - * Validates the given factory. - * - * @param connectionFactory the factory - * @throws SQLException Thrown by one of the factory methods while managing a temporary pooled object. - */ - @SuppressWarnings("resource") - protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) throws SQLException { - PoolableConnection conn = null; - PooledObject p = null; - try { - p = connectionFactory.makeObject(); - conn = p.getObject(); - connectionFactory.activateObject(p); - connectionFactory.validateConnection(conn); - connectionFactory.passivateObject(p); - } finally { - if (p != null) { - connectionFactory.destroyObject(p); - } - } - } - - /** - * The default auto-commit state of connections created by this pool. - */ - private volatile Boolean defaultAutoCommit; - - /** - * The default read-only state of connections created by this pool. - */ - private transient Boolean defaultReadOnly; - - /** - * The default TransactionIsolation state of connections created by this pool. - */ - private volatile int defaultTransactionIsolation = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; - - private Duration defaultQueryTimeoutDuration; - - /** - * The default "catalog" of connections created by this pool. - */ - private volatile String defaultCatalog; - - /** - * The default "schema" of connections created by this pool. - */ - private volatile String defaultSchema; - - /** - * The property that controls if the pooled connections cache some state rather than query the database for current - * state to improve performance. - */ - private boolean cacheState = true; - - /** - * The instance of the JDBC Driver to use. - */ - private Driver driver; - - /** - * The fully qualified Java class name of the JDBC driver to be used. - */ - private String driverClassName; - - /** - * The class loader instance to use to load the JDBC driver. If not specified, {@link Class#forName(String)} is used - * to load the JDBC driver. If specified, {@link Class#forName(String, boolean, ClassLoader)} is used. - */ - private ClassLoader driverClassLoader; - - /** - * True means that borrowObject returns the most recently used ("last in") connection in the pool (if there are idle - * connections available). False means that the pool behaves as a FIFO queue - connections are taken from the idle - * instance pool in the order that they are returned to the pool. - */ - private boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO; - - /** - * The maximum number of active connections that can be allocated from this pool at the same time, or negative for - * no limit. - */ - private int maxTotal = GenericObjectPoolConfig.DEFAULT_MAX_TOTAL; - - /** - * The maximum number of connections that can remain idle in the pool, without extra ones being destroyed, or - * negative for no limit. If maxIdle is set too low on heavily loaded systems it is possible you will see - * connections being closed and almost immediately new connections being opened. This is a result of the active - * threads momentarily closing connections faster than they are opening them, causing the number of idle connections - * to rise above maxIdle. The best value for maxIdle for heavily loaded system will vary but the default is a good - * starting point. - */ - private int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE; - - /** - * The minimum number of active connections that can remain idle in the pool, without extra ones being created when - * the evictor runs, or 0 to create none. The pool attempts to ensure that minIdle connections are available when - * the idle object evictor runs. The value of this property has no effect unless - * {@link #durationBetweenEvictionRuns} has a positive value. - */ - private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE; - - /** - * The initial number of connections that are created when the pool is started. - */ - private int initialSize; - - /** - * The maximum Duration that the pool will wait (when there are no available connections) for a - * connection to be returned before throwing an exception, or <= 0 to wait indefinitely. - */ - private Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT; - - /** - * Prepared statement pooling for this pool. When this property is set to {@code true} both PreparedStatements - * and CallableStatements are pooled. - */ - private boolean poolPreparedStatements; - - private boolean clearStatementPoolOnReturn; - - /** - *

- * The maximum number of open statements that can be allocated from the statement pool at the same time, or negative - * for no limit. Since a connection usually only uses one or two statements at a time, this is mostly used to help - * detect resource leaks. - *

- *

- * Note: As of version 1.3, CallableStatements (those produced by {@link Connection#prepareCall}) are pooled along - * with PreparedStatements (produced by {@link Connection#prepareStatement}) and - * {@code maxOpenPreparedStatements} limits the total number of prepared or callable statements that may be in - * use at a given time. - *

- */ - private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; - - /** - * The indication of whether objects will be validated as soon as they have been created by the pool. If the object - * fails to validate, the borrow operation that triggered the creation will fail. - */ - private boolean testOnCreate; - - /** - * The indication of whether objects will be validated before being borrowed from the pool. If the object fails to - * validate, it will be dropped from the pool, and we will attempt to borrow another. - */ - private boolean testOnBorrow = true; - - /** - * The indication of whether objects will be validated before being returned to the pool. - */ - private boolean testOnReturn; - - /** - * The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle - * object evictor thread will be run. - */ - private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; - - /** - * The number of objects to examine during each run of the idle object evictor thread (if any). - */ - private int numTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; - - /** - * The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle - * object evictor (if any). - */ - private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; - - /** - * The minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the idle - * object evictor, with the extra condition that at least "minIdle" connections remain in the pool. Note that - * {@code minEvictableIdleTimeMillis} takes precedence over this parameter. See - * {@link #getSoftMinEvictableIdleDuration()}. - */ - private Duration softMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION; - - private String evictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME; - - /** - * The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to - * validate, it will be dropped from the pool. - */ - private boolean testWhileIdle; - - /** - * The connection password to be passed to our JDBC driver to establish a connection. - */ - private volatile String password; - - /** - * The connection string to be passed to our JDBC driver to establish a connection. - */ - private String connectionString; - - /** - * The connection user name to be passed to our JDBC driver to establish a connection. - */ - private String userName; - - /** - * The SQL query that will be used to validate connections from this pool before returning them to the caller. If - * specified, this query MUST be an SQL SELECT statement that returns at least one row. If not - * specified, {@link Connection#isValid(int)} will be used to validate connections. - */ - private volatile String validationQuery; - - /** - * Timeout in seconds before connection validation queries fail. - */ - private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); - - /** - * The fully qualified Java class name of a {@link ConnectionFactory} implementation. - */ - private String connectionFactoryClassName; - - /** - * These SQL statements run once after a Connection is created. - *

- * This property can be used for example to run ALTER SESSION SET NLS_SORT=XCYECH in an Oracle Database only once - * after connection creation. - *

- */ - private volatile List connectionInitSqls; - - /** - * Controls access to the underlying connection. - */ - private boolean accessToUnderlyingConnectionAllowed; - - private Duration maxConnDuration = Duration.ofMillis(-1); - - private boolean logExpiredConnections = true; - - private String jmxName; - - private boolean registerConnectionMBean = true; - - private boolean autoCommitOnReturn = true; - - private boolean rollbackOnReturn = true; - - private volatile Set disconnectionSqlCodes; - - /** - * A collection of SQL State codes that are not considered fatal disconnection codes. - * - * @since 2.13.0 - */ - private volatile Set disconnectionIgnoreSqlCodes; - - private boolean fastFailValidation; - - /** - * The object pool that internally manages our connections. - */ - private volatile GenericObjectPool connectionPool; - - /** - * The connection properties that will be sent to our JDBC driver when establishing new connections. - * NOTE - The "user" and "password" properties will be passed explicitly, so they do not need to be - * included here. - */ - private Properties connectionProperties = new Properties(); - - /** - * The data source we will use to manage connections. This object should be acquired ONLY by calls - * to the {@code createDataSource()} method. - */ - private volatile DataSource dataSource; - - /** - * The PrintWriter to which log messages should be directed. - */ - private volatile PrintWriter logWriter = new PrintWriter( - new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); - - private AbandonedConfig abandonedConfig; - - private boolean closed; - - /** - * Actual name under which this component has been registered. - */ - private ObjectNameWrapper registeredJmxObjectName; - - /** - * Adds a custom connection property to the set that will be passed to our JDBC driver. This MUST - * be called before the first connection is retrieved (along with all the other configuration property setters). - * Calls to this method after the connection pool has been initialized have no effect. - * - * @param name Name of the custom connection property - * @param value Value of the custom connection property - */ - public void addConnectionProperty(final String name, final String value) { - connectionProperties.put(name, value); - } - - /** - * Closes and releases all idle connections that are currently stored in the connection pool associated with this - * data source. - *

- * Connections that are checked out to clients when this method is invoked are not affected. When client - * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the - * underlying JDBC connections are closed. - *

- *

- * Attempts to acquire connections using {@link #getConnection()} after this method has been invoked result in - * SQLExceptions. To reopen a datasource that has been closed using this method, use {@link #start()}. - *

- *

- * This method is idempotent - i.e., closing an already closed BasicDataSource has no effect and does not generate - * exceptions. - *

- * - * @throws SQLException if an error occurs closing idle connections - */ - @Override - public synchronized void close() throws SQLException { - if (registeredJmxObjectName != null) { - registeredJmxObjectName.unregisterMBean(); - registeredJmxObjectName = null; - } - closed = true; - final GenericObjectPool oldPool = connectionPool; - connectionPool = null; - dataSource = null; - try { - if (oldPool != null) { - oldPool.close(); - } - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException(Utils.getMessage("pool.close.fail"), e); - } - } - - /** - * Closes the connection pool, silently swallowing any exception that occurs. - */ - private void closeConnectionPool() { - final GenericObjectPool oldPool = connectionPool; - connectionPool = null; - Utils.closeQuietly(oldPool); - } - - /** - * Creates a JDBC connection factory for this data source. The JDBC driver is loaded using the following algorithm: - *
    - *
  1. If a Driver instance has been specified via {@link #setDriver(Driver)} use it
  2. - *
  3. If no Driver instance was specified and {code driverClassName} is specified that class is loaded using the - * {@link ClassLoader} of this class or, if {code driverClassLoader} is set, {code driverClassName} is loaded - * with the specified {@link ClassLoader}.
  4. - *
  5. If {code driverClassName} is specified and the previous attempt fails, the class is loaded using the - * context class loader of the current thread.
  6. - *
  7. If a driver still isn't loaded one is loaded via the {@link DriverManager} using the specified {code connectionString}. - *
- *

- * This method exists so subclasses can replace the implementation class. - *

- * - * @return A new connection factory. - * @throws SQLException If the connection factory cannot be created - */ - protected ConnectionFactory createConnectionFactory() throws SQLException { - // Load the JDBC driver class - return ConnectionFactoryFactory.createConnectionFactory(this, DriverFactory.createDriver(this)); - } - - /** - * Creates a connection pool for this datasource. This method only exists so subclasses can replace the - * implementation class. - *

- * This implementation configures all pool properties other than timeBetweenEvictionRunsMillis. Setting that - * property is deferred to {@link #startPoolMaintenance()}, since setting timeBetweenEvictionRunsMillis to a - * positive value causes {@link GenericObjectPool}'s eviction timer to be started. - *

- * - * @param factory The factory to use to create new connections for this pool. - */ - protected void createConnectionPool(final PoolableConnectionFactory factory) { - // Create an object pool to contain our active connections - final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); - updateJmxName(config); - // Disable JMX on the underlying pool if the DS is not registered: - config.setJmxEnabled(registeredJmxObjectName != null); - // Set up usage tracking if enabled - if (getAbandonedUsageTracking() && abandonedConfig != null) { - abandonedConfig.setUseUsageTracking(true); - } - final GenericObjectPool gop = createObjectPool(factory, config, abandonedConfig); - gop.setMaxTotal(maxTotal); - gop.setMaxIdle(maxIdle); - gop.setMinIdle(minIdle); - gop.setMaxWait(maxWaitDuration); - gop.setTestOnCreate(testOnCreate); - gop.setTestOnBorrow(testOnBorrow); - gop.setTestOnReturn(testOnReturn); - gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun); - gop.setMinEvictableIdleDuration(minEvictableIdleDuration); - gop.setSoftMinEvictableIdleDuration(softMinEvictableIdleDuration); - gop.setTestWhileIdle(testWhileIdle); - gop.setLifo(lifo); - gop.setSwallowedExceptionListener(new SwallowedExceptionLogger(log, logExpiredConnections)); - gop.setEvictionPolicyClassName(evictionPolicyClassName); - factory.setPool(gop); - connectionPool = gop; - } - - /** - * Creates (if necessary) and return the internal data source we are using to manage our connections. - * - * @return The current internal DataSource or a newly created instance if it has not yet been created. - * @throws SQLException if the object pool cannot be created. - */ - protected synchronized DataSource createDataSource() throws SQLException { - if (closed) { - throw new SQLException("Data source is closed"); - } - - // Return the pool if we have already created it - // This is double-checked locking. This is safe since dataSource is - // volatile and the code is targeted at Java 5 onwards. - if (dataSource != null) { - return dataSource; - } - synchronized (this) { - if (dataSource != null) { - return dataSource; - } - jmxRegister(); - - // create factory which returns raw physical connections - final ConnectionFactory driverConnectionFactory = createConnectionFactory(); - - // Set up the poolable connection factory - final PoolableConnectionFactory poolableConnectionFactory; - try { - poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory); - poolableConnectionFactory.setPoolStatements(poolPreparedStatements); - poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); - // create a pool for our connections - createConnectionPool(poolableConnectionFactory); - final DataSource newDataSource = createDataSourceInstance(); - newDataSource.setLogWriter(logWriter); - connectionPool.addObjects(initialSize); - // If timeBetweenEvictionRunsMillis > 0, start the pool's evictor - // task - startPoolMaintenance(); - dataSource = newDataSource; - } catch (final SQLException | RuntimeException se) { - closeConnectionPool(); - throw se; - } catch (final Exception ex) { - closeConnectionPool(); - throw new SQLException("Error creating connection factory", ex); - } - - return dataSource; - } - } - - /** - * Creates the actual data source instance. This method only exists so that subclasses can replace the - * implementation class. - * - * @throws SQLException if unable to create a datasource instance - * @return A new DataSource instance - */ - protected DataSource createDataSourceInstance() throws SQLException { - final PoolingDataSource pds = new PoolingDataSource<>(connectionPool); - pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); - return pds; - } - - /** - * Creates an object pool used to provide pooling support for {@link Connection JDBC connections}. - * - * @param factory the object factory - * @param poolConfig the object pool configuration - * @param abandonedConfig the abandoned objects configuration - * @return a non-null instance - */ - protected GenericObjectPool createObjectPool(final PoolableConnectionFactory factory, - final GenericObjectPoolConfig poolConfig, final AbandonedConfig abandonedConfig) { - final GenericObjectPool gop; - if (abandonedConfig != null && (abandonedConfig.getRemoveAbandonedOnBorrow() - || abandonedConfig.getRemoveAbandonedOnMaintenance())) { - gop = new GenericObjectPool<>(factory, poolConfig, abandonedConfig); - } else { - gop = new GenericObjectPool<>(factory, poolConfig); - } - return gop; - } - - /** - * Creates the PoolableConnectionFactory and attaches it to the connection pool. This method only exists so - * subclasses can replace the default implementation. - * - * @param driverConnectionFactory JDBC connection factory - * @throws SQLException if an error occurs creating the PoolableConnectionFactory - * @return A new PoolableConnectionFactory configured with the current configuration of this BasicDataSource - */ - protected PoolableConnectionFactory createPoolableConnectionFactory(final ConnectionFactory driverConnectionFactory) - throws SQLException { - PoolableConnectionFactory connectionFactory = null; - try { - if (registerConnectionMBean) { - connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, ObjectNameWrapper.unwrap(registeredJmxObjectName)); - } else { - connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, null); - } - connectionFactory.setValidationQuery(validationQuery); - connectionFactory.setValidationQueryTimeout(validationQueryTimeoutDuration); - connectionFactory.setConnectionInitSql(connectionInitSqls); - connectionFactory.setDefaultReadOnly(defaultReadOnly); - connectionFactory.setDefaultAutoCommit(defaultAutoCommit); - connectionFactory.setDefaultTransactionIsolation(defaultTransactionIsolation); - connectionFactory.setDefaultCatalog(defaultCatalog); - connectionFactory.setDefaultSchema(defaultSchema); - connectionFactory.setCacheState(cacheState); - connectionFactory.setPoolStatements(poolPreparedStatements); - connectionFactory.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); - connectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); - connectionFactory.setMaxConn(maxConnDuration); - connectionFactory.setRollbackOnReturn(getRollbackOnReturn()); - connectionFactory.setAutoCommitOnReturn(getAutoCommitOnReturn()); - connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration()); - connectionFactory.setFastFailValidation(fastFailValidation); - connectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes); - connectionFactory.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes); - validateConnectionFactory(connectionFactory); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e); - } - return connectionFactory; - } - - /** - * Manually evicts idle connections - * - * @throws Exception when there is a problem evicting idle objects. - */ - public void evict() throws Exception { - if (connectionPool != null) { - connectionPool.evict(); - } - } - - /** - * Gets the print writer used by this configuration to log information on abandoned objects. - * - * @return The print writer used by this configuration to log information on abandoned objects. - */ - public PrintWriter getAbandonedLogWriter() { - return abandonedConfig == null ? null : abandonedConfig.getLogWriter(); - } - - /** - * If the connection pool implements {@link org.apache.commons.pool2.UsageTracking UsageTracking}, should the - * connection pool record a stack trace every time a method is called on a pooled connection and retain the most - * recent stack trace to aid debugging of abandoned connections? - * - * @return {@code true} if usage tracking is enabled - */ - @Override - public boolean getAbandonedUsageTracking() { - return abandonedConfig != null && abandonedConfig.getUseUsageTracking(); - } - - /** - * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked - * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit - * setting is {@code false} when the connection is returned. It is {@code true} by default. - * - * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit. - */ - public boolean getAutoCommitOnReturn() { - return autoCommitOnReturn; - } - - /** - * Gets the state caching flag. - * - * @return the state caching flag - */ - @Override - public boolean getCacheState() { - return cacheState; - } - - /** - * Creates (if necessary) and return a connection to the database. - * - * @throws SQLException if a database access error occurs - * @return a database connection - */ - @Override - public Connection getConnection() throws SQLException { - if (Utils.isSecurityEnabled()) { - final PrivilegedExceptionAction action = () -> createDataSource().getConnection(); - try { - return AccessController.doPrivileged(action); - } catch (final PrivilegedActionException e) { - final Throwable cause = e.getCause(); - if (cause instanceof SQLException) { - throw (SQLException) cause; - } - throw new SQLException(e); - } - } - return createDataSource().getConnection(); - } - - /** - * BasicDataSource does NOT support this method. - * - * @param user Database user on whose behalf the Connection is being made - * @param pass The database user's password - * @throws UnsupportedOperationException always thrown. - * @throws SQLException if a database access error occurs - * @return nothing - always throws UnsupportedOperationException - */ - @Override - public Connection getConnection(final String user, final String pass) throws SQLException { - // This method isn't supported by the PoolingDataSource returned by the - // createDataSource - throw new UnsupportedOperationException("Not supported by BasicDataSource"); - } - - /** - * Gets the ConnectionFactoryClassName that has been configured for use by this pool. - *

- * Note: This getter only returns the last value set by a call to {@link #setConnectionFactoryClassName(String)}. - *

- * - * @return the ConnectionFactoryClassName that has been configured for use by this pool. - * @since 2.7.0 - */ - public String getConnectionFactoryClassName() { - return this.connectionFactoryClassName; - } - - /** - * Gets the list of SQL statements executed when a physical connection is first created. Returns an empty list if - * there are no initialization statements configured. - * - * @return initialization SQL statements - */ - public List getConnectionInitSqls() { - final List result = connectionInitSqls; - return result == null ? Collections.emptyList() : result; - } - - /** - * Provides the same data as {@link #getConnectionInitSqls()} but in an array so it is accessible via JMX. - */ - @Override - public String[] getConnectionInitSqlsAsArray() { - return getConnectionInitSqls().toArray(Utils.EMPTY_STRING_ARRAY); - } - - /** - * Gets the underlying connection pool. - * - * @return the underlying connection pool. - * @since 2.10.0 - */ - public GenericObjectPool getConnectionPool() { - return connectionPool; - } - - Properties getConnectionProperties() { - return connectionProperties; - } - - /** - * Gets the default auto-commit property. - * - * @return true if default auto-commit is enabled - */ - @Override - public Boolean getDefaultAutoCommit() { - return defaultAutoCommit; - } - - /** - * Gets the default catalog. - * - * @return the default catalog - */ - @Override - public String getDefaultCatalog() { - return this.defaultCatalog; - } - - /** - * Gets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this - * connection. {@code null} means that the driver default will be used. - * - * @return The default query timeout in seconds. - * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. - */ - @Deprecated - public Integer getDefaultQueryTimeout() { - return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); - } - - /** - * Gets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this - * connection. {@code null} means that the driver default will be used. - * - * @return The default query timeout Duration. - * @since 2.10.0 - */ - public Duration getDefaultQueryTimeoutDuration() { - return defaultQueryTimeoutDuration; - } - - /** - * Gets the default readOnly property. - * - * @return true if connections are readOnly by default - */ - @Override - public Boolean getDefaultReadOnly() { - return defaultReadOnly; - } - - /** - * Gets the default schema. - * - * @return the default schema. - * @since 2.5.0 - */ - @Override - public String getDefaultSchema() { - return this.defaultSchema; - } - - /** - * Gets the default transaction isolation state of returned connections. - * - * @return the default value for transaction isolation state - * @see Connection#getTransactionIsolation - */ - @Override - public int getDefaultTransactionIsolation() { - return this.defaultTransactionIsolation; - } - - /** - * Gets the set of SQL State codes that are not considered fatal disconnection codes. - *

- * This method returns the set of SQL State codes that have been specified to be ignored - * when determining if a {@link SQLException} signals a disconnection. These codes will not - * trigger a disconnection even if they match other disconnection criteria. - *

- * - * @return a set of SQL State codes that should be ignored for disconnection checks, or an empty set if none have been specified. - * @since 2.13.0 - */ - public Set getDisconnectionIgnoreSqlCodes() { - final Set result = disconnectionIgnoreSqlCodes; - return result == null ? Collections.emptySet() : result; - } - - /** - * Provides the same data as {@link #getDisconnectionIgnoreSqlCodes()} but in an array, so it is accessible via JMX. - * - * @since 2.13.0 - */ - @Override - public String[] getDisconnectionIgnoreSqlCodesAsArray() { - return getDisconnectionIgnoreSqlCodes().toArray(Utils.EMPTY_STRING_ARRAY); - } - - /** - * Gets the set of SQL State codes considered to signal fatal conditions. - * - * @return fatal disconnection state codes - * @see #setDisconnectionSqlCodes(Collection) - * @since 2.1 - */ - public Set getDisconnectionSqlCodes() { - final Set result = disconnectionSqlCodes; - return result == null ? Collections.emptySet() : result; - } - - /** - * Provides the same data as {@link #getDisconnectionSqlCodes} but in an array so it is accessible via JMX. - * - * @since 2.1 - */ - @Override - public String[] getDisconnectionSqlCodesAsArray() { - return getDisconnectionSqlCodes().toArray(Utils.EMPTY_STRING_ARRAY); - } - - /** - * Gets the JDBC Driver that has been configured for use by this pool. - *

- * Note: This getter only returns the last value set by a call to {@link #setDriver(Driver)}. It does not return any - * driver instance that may have been created from the value set via {@link #setDriverClassName(String)}. - *

- * - * @return the JDBC Driver that has been configured for use by this pool - */ - public synchronized Driver getDriver() { - return driver; - } - - /** - * Gets the class loader specified for loading the JDBC driver. Returns {@code null} if no class loader has - * been explicitly specified. - *

- * Note: This getter only returns the last value set by a call to {@link #setDriverClassLoader(ClassLoader)}. It - * does not return the class loader of any driver that may have been set via {@link #setDriver(Driver)}. - *

- * - * @return The class loader specified for loading the JDBC driver. - */ - public synchronized ClassLoader getDriverClassLoader() { - return this.driverClassLoader; - } - - /** - * Gets the JDBC driver class name. - *

- * Note: This getter only returns the last value set by a call to {@link #setDriverClassName(String)}. It does not - * return the class name of any driver that may have been set via {@link #setDriver(Driver)}. - *

- * - * @return the JDBC driver class name - */ - @Override - public synchronized String getDriverClassName() { - return this.driverClassName; - } - - /** - * Gets the value of the {code durationBetweenEvictionRuns} property. - * - * @return the time (in milliseconds) between evictor runs - * @see #setDurationBetweenEvictionRuns(Duration) - * @since 2.10.0 - */ - public synchronized Duration getDurationBetweenEvictionRuns() { - return this.durationBetweenEvictionRuns; - } - - /** - * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked - * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit - * setting is {@code false} when the connection is returned. It is {@code true} by default. - * - * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit. - * @deprecated Use {@link #getAutoCommitOnReturn()}. - */ - @Deprecated - public boolean getEnableAutoCommitOnReturn() { - return autoCommitOnReturn; - } - - /** - * Gets the EvictionPolicy implementation in use with this connection pool. - * - * @return The EvictionPolicy implementation in use with this connection pool. - */ - public synchronized String getEvictionPolicyClassName() { - return evictionPolicyClassName; - } - - /** - * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with - * SQL State indicating fatal disconnection errors. - * - * @return true if connections created by this datasource will fast fail validation. - * @see #setDisconnectionSqlCodes(Collection) - * @see #setDisconnectionIgnoreSqlCodes(Collection) - * @since 2.1 - */ - @Override - public boolean getFastFailValidation() { - return fastFailValidation; - } - - /** - * Gets the initial size of the connection pool. - * - * @return the number of connections created when the pool is initialized - */ - @Override - public synchronized int getInitialSize() { - return this.initialSize; - } - - /** - * Gets the JMX name that has been requested for this DataSource. If the requested name is not valid, an - * alternative may be chosen. - * - * @return The JMX name that has been requested for this DataSource. - */ - public String getJmxName() { - return jmxName; - } - - /** - * Gets the LIFO property. - * - * @return true if connection pool behaves as a LIFO queue. - */ - @Override - public synchronized boolean getLifo() { - return this.lifo; - } - - /** - * Flag to log stack traces for application code which abandoned a Statement or Connection. - *

- * Defaults to false. - *

- *

- * Logging of abandoned Statements and Connections adds overhead for every Connection open or new Statement because - * a stack trace has to be generated. - *

- */ - @Override - public boolean getLogAbandoned() { - return abandonedConfig != null && abandonedConfig.getLogAbandoned(); - } - - /** - * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or - * not log messages are generated when the pool closes connections due to maximum lifetime exceeded. - * - * @since 2.1 - */ - @Override - public boolean getLogExpiredConnections() { - return logExpiredConnections; - } - - /** - * BasicDataSource does NOT support this method. - * - *

- * Gets the login timeout (in seconds) for connecting to the database. - *

- *

- * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. - *

- * - * @throws SQLException if a database access error occurs - * @throws UnsupportedOperationException If the DataSource implementation does not support the login timeout - * feature. - * @return login timeout in seconds - */ - @Override - public int getLoginTimeout() throws SQLException { - // This method isn't supported by the PoolingDataSource returned by the createDataSource - throw new UnsupportedOperationException("Not supported by BasicDataSource"); - } - - /** - * Gets the log writer being used by this data source. - *

- * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. - *

- * - * @throws SQLException if a database access error occurs - * @return log writer in use - */ - @Override - public PrintWriter getLogWriter() throws SQLException { - return createDataSource().getLogWriter(); - } - - /** - * Gets the maximum permitted duration of a connection. A value of zero or less indicates an - * infinite lifetime. - * @return the maximum permitted duration of a connection. - * @since 2.10.0 - */ - public Duration getMaxConnDuration() { - return maxConnDuration; - } - - /** - * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an - * infinite lifetime. - * @deprecated Use {@link #getMaxConnDuration()}. - */ - @Override - @Deprecated - public long getMaxConnLifetimeMillis() { - return maxConnDuration.toMillis(); - } - - /** - * Gets the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed - * on return to the pool. - *

- * A negative value indicates that there is no limit - *

- * - * @return the maximum number of idle connections - */ - @Override - public synchronized int getMaxIdle() { - return this.maxIdle; - } - - /** - * Gets the value of the {@code maxOpenPreparedStatements} property. - * - * @return the maximum number of open statements - */ - @Override - public synchronized int getMaxOpenPreparedStatements() { - return this.maxOpenPreparedStatements; - } - - /** - * Gets the maximum number of active connections that can be allocated at the same time. - *

- * A negative number means that there is no limit. - *

- * - * @return the maximum number of active connections - */ - @Override - public synchronized int getMaxTotal() { - return this.maxTotal; - } - - /** - * Gets the maximum Duration that the pool will wait for a connection to be returned before throwing an exception. A - * value less than or equal to zero means the pool is set to wait indefinitely. - * - * @return the maxWaitDuration property value. - * @since 2.10.0 - */ - public synchronized Duration getMaxWaitDuration() { - return this.maxWaitDuration; - } - - /** - * Gets the maximum number of milliseconds that the pool will wait for a connection to be returned before - * throwing an exception. A value less than or equal to zero means the pool is set to wait indefinitely. - * - * @return the maxWaitMillis property value. - * @deprecated Use {@link #getMaxWaitDuration()}. - */ - @Deprecated - @Override - public synchronized long getMaxWaitMillis() { - return this.maxWaitDuration.toMillis(); - } - - /** - * Gets the {code minEvictableIdleDuration} property. - * - * @return the value of the {code minEvictableIdleDuration} property - * @see #setMinEvictableIdle(Duration) - * @since 2.10.0 - */ - public synchronized Duration getMinEvictableIdleDuration() { - return this.minEvictableIdleDuration; - } - - /** - * Gets the {code minEvictableIdleDuration} property. - * - * @return the value of the {code minEvictableIdleDuration} property - * @see #setMinEvictableIdle(Duration) - * @deprecated Use {@link #getMinEvictableIdleDuration()}. - */ - @Deprecated - @Override - public synchronized long getMinEvictableIdleTimeMillis() { - return this.minEvictableIdleDuration.toMillis(); - } - - /** - * Gets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections - * are available when the idle object evictor runs. The value of this property has no effect unless - * {code durationBetweenEvictionRuns} has a positive value. - * - * @return the minimum number of idle connections - * @see GenericObjectPool#getMinIdle() - */ - @Override - public synchronized int getMinIdle() { - return this.minIdle; - } - - /** - * [Read Only] The current number of active connections that have been allocated from this data source. - * - * @return the current number of active connections - */ - @Override - public int getNumActive() { - // Copy reference to avoid NPE if close happens after null check - final GenericObjectPool pool = connectionPool; - return pool == null ? 0 : pool.getNumActive(); - } - - /** - * [Read Only] The current number of idle connections that are waiting to be allocated from this data source. - * - * @return the current number of idle connections - */ - @Override - public int getNumIdle() { - // Copy reference to avoid NPE if close happens after null check - final GenericObjectPool pool = connectionPool; - return pool == null ? 0 : pool.getNumIdle(); - } - - /** - * Gets the value of the {code numTestsPerEvictionRun} property. - * - * @return the number of objects to examine during idle object evictor runs - * @see #setNumTestsPerEvictionRun(int) - */ - @Override - public synchronized int getNumTestsPerEvictionRun() { - return this.numTestsPerEvictionRun; - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(); - } - - /** - * Gets the password passed to the JDBC driver to establish connections. - * - * @return the connection password - * @deprecated Exposing passwords via JMX is an Information Exposure issue. - */ - @Deprecated - @Override - public String getPassword() { - return this.password; - } - - /** - * Gets the registered JMX ObjectName. - * - * @return the registered JMX ObjectName. - */ - protected ObjectName getRegisteredJmxName() { - return ObjectNameWrapper.unwrap(registeredJmxObjectName); - } - - /** - * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout when borrowObject is invoked. - *

- * The default value is false. - *

- *

- * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more - * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds. - *

- *

- * Abandoned connections are identified and removed when {@link #getConnection()} is invoked and all of the - * following conditions hold: - *

- *
    - *
  • {@link #getRemoveAbandonedOnBorrow()}
  • - *
  • {@link #getNumActive()} > {@link #getMaxTotal()} - 3
  • - *
  • {@link #getNumIdle()} < 2
  • - *
- * - * @see #getRemoveAbandonedTimeoutDuration() - */ - @Override - public boolean getRemoveAbandonedOnBorrow() { - return abandonedConfig != null && abandonedConfig.getRemoveAbandonedOnBorrow(); - } - - /** - * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout during pool maintenance. - *

- * The default value is false. - *

- *

- * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more - * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds. - *

- * - * @see #getRemoveAbandonedTimeoutDuration() - */ - @Override - public boolean getRemoveAbandonedOnMaintenance() { - return abandonedConfig != null && abandonedConfig.getRemoveAbandonedOnMaintenance(); - } - - /** - * Gets the timeout in seconds before an abandoned connection can be removed. - *

- * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one - * of the execute methods) resets the lastUsed property of the parent connection. - *

- *

- * Abandoned connection cleanup happens when: - *

- *
    - *
  • {@link #getRemoveAbandonedOnBorrow()} or {@link #getRemoveAbandonedOnMaintenance()} = true
  • - *
  • {@link #getNumIdle() numIdle} < 2
  • - *
  • {@link #getNumActive() numActive} > {@link #getMaxTotal() maxTotal} - 3
  • - *
- *

- * The default value is 300 seconds. - *

- * @deprecated Use {@link #getRemoveAbandonedTimeoutDuration()}. - */ - @Deprecated - @Override - public int getRemoveAbandonedTimeout() { - return (int) getRemoveAbandonedTimeoutDuration().getSeconds(); - } - - /** - * Gets the timeout before an abandoned connection can be removed. - *

- * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one - * of the execute methods) resets the lastUsed property of the parent connection. - *

- *

- * Abandoned connection cleanup happens when: - *

- *
    - *
  • {@link #getRemoveAbandonedOnBorrow()} or {@link #getRemoveAbandonedOnMaintenance()} = true
  • - *
  • {@link #getNumIdle() numIdle} < 2
  • - *
  • {@link #getNumActive() numActive} > {@link #getMaxTotal() maxTotal} - 3
  • - *
- *

- * The default value is 300 seconds. - *

- * @return Timeout before an abandoned connection can be removed. - * @since 2.10.0 - */ - public Duration getRemoveAbandonedTimeoutDuration() { - return abandonedConfig == null ? Duration.ofSeconds(300) : abandonedConfig.getRemoveAbandonedTimeoutDuration(); - } - - /** - * Gets the current value of the flag that controls whether a connection will be rolled back when it is returned to - * the pool if auto commit is not enabled and the connection is not read only. - * - * @return whether a connection will be rolled back when it is returned to the pool. - */ - public boolean getRollbackOnReturn() { - return rollbackOnReturn; - } - - /** - * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by - * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. - *

- * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value, - * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are - * visited by the evictor, idle time is first compared against {@code minEvictableIdleTimeMillis} (without - * considering the number of idle connections in the pool) and then against {@code softMinEvictableIdleTimeMillis}, - * including the {@code minIdle}, constraint. - *

- * - * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming - * there are minIdle idle connections in the pool - * @since 2.10.0 - */ - public synchronized Duration getSoftMinEvictableIdleDuration() { - return softMinEvictableIdleDuration; - } - - /** - * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by - * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. - *

- * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value, - * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are - * visited by the evictor, idle time is first compared against {@code minEvictableIdleTimeMillis} (without - * considering the number of idle connections in the pool) and then against {@code softMinEvictableIdleTimeMillis}, - * including the {@code minIdle}, constraint. - *

- * - * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming - * there are minIdle idle connections in the pool - * @deprecated Use {@link #getSoftMinEvictableIdleDuration()}. - */ - @Deprecated - @Override - public synchronized long getSoftMinEvictableIdleTimeMillis() { - return softMinEvictableIdleDuration.toMillis(); - } - - /** - * Gets the {code testOnBorrow} property. - * - * @return true if objects are validated before being borrowed from the pool - * @see #setTestOnBorrow(boolean) - */ - @Override - public synchronized boolean getTestOnBorrow() { - return this.testOnBorrow; - } - - /** - * Gets the {code testOnCreate} property. - * - * @return true if objects are validated immediately after they are created by the pool - * @see #setTestOnCreate(boolean) - */ - @Override - public synchronized boolean getTestOnCreate() { - return this.testOnCreate; - } - - /** - * Gets the value of the {code testOnReturn} property. - * - * @return true if objects are validated before being returned to the pool - * @see #setTestOnReturn(boolean) - */ - public synchronized boolean getTestOnReturn() { - return this.testOnReturn; - } - - /** - * Gets the value of the {code testWhileIdle} property. - * - * @return true if objects examined by the idle object evictor are validated - * @see #setTestWhileIdle(boolean) - */ - @Override - public synchronized boolean getTestWhileIdle() { - return this.testWhileIdle; - } - - /** - * Gets the value of the {code durationBetweenEvictionRuns} property. - * - * @return the time (in milliseconds) between evictor runs - * @see #setDurationBetweenEvictionRuns(Duration) - * @deprecated Use {@link #getDurationBetweenEvictionRuns()}. - */ - @Deprecated - @Override - public synchronized long getTimeBetweenEvictionRunsMillis() { - return this.durationBetweenEvictionRuns.toMillis(); - } - - /** - * Gets the JDBC connection {code connectionString} property. - * - * @return the {code connectionString} passed to the JDBC driver to establish connections - */ - @Override - public synchronized String getUrl() { - return this.connectionString; - } - - /** - * Gets the JDBC connection {code userName} property. - * - * @return the {code userName} passed to the JDBC driver to establish connections - * @deprecated Use {@link #getUserName()}. - */ - @Deprecated - @Override - public String getUsername() { - return this.userName; - } - - /** - * Gets the validation query used to validate connections before returning them. - * - * @return the SQL validation query - * @see #setValidationQuery(String) - */ - @Override - public String getValidationQuery() { - return this.validationQuery; - } - - /** - * Gets the validation query timeout. - * - * @return the timeout in seconds before connection validation queries fail. - * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. - */ - @Deprecated - @Override - public int getValidationQueryTimeout() { - return (int) validationQueryTimeoutDuration.getSeconds(); - } - - /** - * Gets the validation query timeout. - * - * @return the timeout in seconds before connection validation queries fail. - */ - public Duration getValidationQueryTimeoutDuration() { - return validationQueryTimeoutDuration; - } - - /** - * Manually invalidates a connection, effectively requesting the pool to try to close it, remove it from the pool - * and reclaim pool capacity. - * - * @param connection The Connection to invalidate. - * @throws IllegalStateException if invalidating the connection failed. - * @since 2.1 - */ - @SuppressWarnings("resource") - public void invalidateConnection(final Connection connection) throws IllegalStateException { - if (connection == null) { - return; - } - if (connectionPool == null) { - throw new IllegalStateException("Cannot invalidate connection: ConnectionPool is null."); - } - - final PoolableConnection poolableConnection; - try { - poolableConnection = connection.unwrap(PoolableConnection.class); - if (poolableConnection == null) { - throw new IllegalStateException( - "Cannot invalidate connection: Connection is not a poolable connection."); - } - } catch (final SQLException e) { - throw new IllegalStateException("Cannot invalidate connection: Unwrapping poolable connection failed.", e); - } - - try { - connectionPool.invalidateObject(poolableConnection); - } catch (final Exception e) { - throw new IllegalStateException("Invalidating connection threw unexpected exception", e); - } - } - - /** - * Gets the value of the accessToUnderlyingConnectionAllowed property. - * - * @return true if access to the underlying connection is allowed, false otherwise. - */ - @Override - public synchronized boolean isAccessToUnderlyingConnectionAllowed() { - return this.accessToUnderlyingConnectionAllowed; - } - - /** - * Returns true if the statement pool is cleared when the connection is returned to its pool. - * - * @return true if the statement pool is cleared at connection return - * @since 2.8.0 - */ - @Override - public boolean isClearStatementPoolOnReturn() { - return clearStatementPoolOnReturn; - } - - /** - * If true, this data source is closed and no more connections can be retrieved from this data source. - * - * @return true, if the data source is closed; false otherwise - */ - @Override - public synchronized boolean isClosed() { - return closed; - } - - /** - * Delegates in a null-safe manner to {@link String#isEmpty()}. - * - * @param value the string to test, may be null. - * @return boolean false if value is null, otherwise {@link String#isEmpty()}. - */ - private boolean isEmpty(final String value) { - return value == null || value.trim().isEmpty(); - } - - /** - * Returns true if we are pooling statements. - * - * @return true if prepared and callable statements are pooled - */ - @Override - public synchronized boolean isPoolPreparedStatements() { - return this.poolPreparedStatements; - } - - @Override - public boolean isWrapperFor(final Class iface) throws SQLException { - return iface != null && iface.isInstance(this); - } - - private void jmxRegister() { - // Return immediately if this DataSource has already been registered - if (registeredJmxObjectName != null) { - return; - } - // Return immediately if no JMX name has been specified - final String requestedName = getJmxName(); - if (requestedName == null) { - return; - } - registeredJmxObjectName = registerJmxObjectName(requestedName, null); - try { - final StandardMBean standardMBean = new StandardMBean(this, DataSourceMXBean.class); - registeredJmxObjectName.registerMBean(standardMBean); - } catch (final NotCompliantMBeanException e) { - log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored."); - } - } - - /** - * Logs the given message. - * - * @param message the message to log. - */ - protected void log(final String message) { - if (logWriter != null) { - logWriter.println(message); - } - } - - /** - * Logs the given message and throwable. - * - * @param message value to be log. - * @param throwable the throwable. - * @since 2.7.0 - */ - protected void log(final String message, final Throwable throwable) { - if (logWriter != null) { - logWriter.println(message); - throwable.printStackTrace(logWriter); - } - } - - @Override - public void postDeregister() { - // NO-OP - } - - @Override - public void postRegister(final Boolean registrationDone) { - // NO-OP - } - - @Override - public void preDeregister() throws Exception { - // NO-OP - } - - @Override - public ObjectName preRegister(final MBeanServer server, final ObjectName objectName) { - registeredJmxObjectName = registerJmxObjectName(getJmxName(), objectName); - return ObjectNameWrapper.unwrap(registeredJmxObjectName); - } - - private ObjectNameWrapper registerJmxObjectName(final String requestedName, final ObjectName objectName) { - ObjectNameWrapper objectNameWrapper = null; - if (requestedName != null) { - try { - objectNameWrapper = ObjectNameWrapper.wrap(requestedName); - } catch (final MalformedObjectNameException e) { - log.warn("The requested JMX name '" + requestedName + "' was not valid and will be ignored."); - } - } - if (objectNameWrapper == null) { - objectNameWrapper = ObjectNameWrapper.wrap(objectName); - } - return objectNameWrapper; - } - - /** - * Removes a custom connection property. - * - * @param name Name of the custom connection property to remove - * @see #addConnectionProperty(String, String) - */ - public void removeConnectionProperty(final String name) { - connectionProperties.remove(name); - } - - /** - * Restarts the datasource. - *

- * This method calls {@link #close()} and {@link #start()} in sequence within synchronized scope so any - * connection requests that come in while the datasource is shutting down will be served by the new pool. - *

- * Idle connections that are stored in the connection pool when this method is invoked are closed, but - * connections that are checked out to clients when this method is invoked are not affected. When client - * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the - * underlying JDBC connections are closed. These connections do not count in {@link #getMaxTotal()} or - * {@link #getNumActive()} after invoking this method. For example, if there are 3 connections checked out by - * clients when {@link #restart()} is invoked, after this method is called, {@link #getNumActive()} will - * return 0 and up to {@link #getMaxTotal()} + 3 connections may be open until the connections sourced from - * the original pool are returned. - *

- * The new connection pool created by this method is initialized with currently set configuration properties. - * - * @throws SQLException if an error occurs initializing the datasource - */ - @Override - public synchronized void restart() throws SQLException { - close(); - start(); - } - - private void setAbandoned(final BiConsumer consumer, final T object) { - if (abandonedConfig == null) { - abandonedConfig = new AbandonedConfig(); - } - consumer.accept(abandonedConfig, object); - final GenericObjectPool gop = this.connectionPool; - if (gop != null) { - gop.setAbandonedConfig(abandonedConfig); - } - } - - /** - * Sets the print writer to be used by this configuration to log information on abandoned objects. - * - * @param logWriter The new log writer - */ - public void setAbandonedLogWriter(final PrintWriter logWriter) { - setAbandoned(AbandonedConfig::setLogWriter, logWriter); - } - - /** - * If the connection pool implements {@link org.apache.commons.pool2.UsageTracking UsageTracking}, configure whether - * the connection pool should record a stack trace every time a method is called on a pooled connection and retain - * the most recent stack trace to aid debugging of abandoned connections. - * - * @param usageTracking A value of {@code true} will enable the recording of a stack trace on every use of a - * pooled connection - */ - public void setAbandonedUsageTracking(final boolean usageTracking) { - setAbandoned(AbandonedConfig::setUseUsageTracking, usageTracking); - } - - /** - * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to - * the underlying connection. (Default: false) - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param allow Access to the underlying connection is granted when true. - */ - public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { - this.accessToUnderlyingConnectionAllowed = allow; - } - - /** - * Sets the value of the flag that controls whether or not connections being returned to the pool will be checked - * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit - * setting is {@code false} when the connection is returned. It is {@code true} by default. - * - * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured - * with auto-commit. - * @since 2.6.0 - */ - public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { - this.autoCommitOnReturn = autoCommitOnReturn; - } - - /** - * Sets the state caching flag. - * - * @param cacheState The new value for the state caching flag - */ - public void setCacheState(final boolean cacheState) { - this.cacheState = cacheState; - } - - /** - * Sets whether the pool of statements (which was enabled with {@link #setPoolPreparedStatements(boolean)}) should - * be cleared when the connection is returned to its pool. Default is false. - * - * @param clearStatementPoolOnReturn clear or not - * @since 2.8.0 - */ - public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { - this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; - } - - /** - * Sets the ConnectionFactory class name. - * - * @param connectionFactoryClassName A class name. - * @since 2.7.0 - */ - public void setConnectionFactoryClassName(final String connectionFactoryClassName) { - this.connectionFactoryClassName = isEmpty(connectionFactoryClassName) ? null : connectionFactoryClassName; - } - - /** - * Sets the collection of SQL statements to be executed when a physical connection is first created. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param connectionInitSqls Collection of SQL statements to execute on connection creation - */ - public void setConnectionInitSqls(final Collection connectionInitSqls) { - final List collect = Utils.isEmpty(connectionInitSqls) ? null - : connectionInitSqls.stream().filter(s -> !isEmpty(s)).collect(Collectors.toList()); - this.connectionInitSqls = Utils.isEmpty(collect) ? null : collect; - } - - /** - * Sets the list of SQL statements to be executed when a physical connection is first created. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param connectionInitSqls List of SQL statements to execute on connection creation - * @since 2.12.0 - */ - public void setConnectionInitSqls(final List connectionInitSqls) { - setConnectionInitSqls((Collection) connectionInitSqls); - } - - private void setConnectionPool(final BiConsumer, T> consumer, final T object) { - if (connectionPool != null) { - consumer.accept(connectionPool, object); - } - } - - /** - * Sets the connection properties passed to driver.connect(...). - *

- * Format of the string must be [propertyName=property;]* - *

- *

- * NOTE - The "user" and "password" properties will be added explicitly, so they do not need to be included here. - *

- * - * @param connectionProperties the connection properties used to create new connections - */ - public void setConnectionProperties(final String connectionProperties) { - Objects.requireNonNull(connectionProperties, "connectionProperties"); - final String[] entries = connectionProperties.split(";"); - final Properties properties = new Properties(); - Stream.of(entries).filter(e -> !e.isEmpty()).forEach(entry -> { - final int index = entry.indexOf('='); - if (index > 0) { - final String name = entry.substring(0, index); - final String value = entry.substring(index + 1); - properties.setProperty(name, value); - } else { - // no value is empty string which is how - // java.util.Properties works - properties.setProperty(entry, ""); - } - }); - this.connectionProperties = properties; - } - - /** - * Sets default auto-commit state of connections returned by this datasource. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param defaultAutoCommit default auto-commit value - */ - public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { - this.defaultAutoCommit = defaultAutoCommit; - } - - /** - * Sets the default catalog. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param defaultCatalog the default catalog - */ - public void setDefaultCatalog(final String defaultCatalog) { - this.defaultCatalog = isEmpty(defaultCatalog) ? null : defaultCatalog; - } - - /** - * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this - * connection. {@code null} means that the driver default will be used. - * - * @param defaultQueryTimeoutDuration The default query timeout Duration. - * @since 2.10.0 - */ - public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; - } - - /** - * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this - * connection. {@code null} means that the driver default will be used. - * - * @param defaultQueryTimeoutSeconds The default query timeout in seconds. - * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. - */ - @Deprecated - public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); - } - - /** - * Sets defaultReadonly property. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param defaultReadOnly default read-only value - */ - public void setDefaultReadOnly(final Boolean defaultReadOnly) { - this.defaultReadOnly = defaultReadOnly; - } - - /** - * Sets the default schema. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param defaultSchema the default catalog - * @since 2.5.0 - */ - public void setDefaultSchema(final String defaultSchema) { - this.defaultSchema = isEmpty(defaultSchema) ? null : defaultSchema; - } - - /** - * Sets the default transaction isolation state for returned connections. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param defaultTransactionIsolation the default transaction isolation state - * @see Connection#getTransactionIsolation - */ - public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { - this.defaultTransactionIsolation = defaultTransactionIsolation; - } - - /** - * Sets the SQL State codes that should be ignored when determining fatal disconnection conditions. - *

- * This method allows you to specify a collection of SQL State codes that will be excluded from - * disconnection checks. These codes will not trigger the "fatally disconnected" status even if they - * match the typical disconnection criteria. This can be useful in scenarios where certain SQL State - * codes (e.g., specific codes starting with "08") are known to be non-fatal in your environment. - *

- *

- * The effect of this method is similar to the one described in {@link #setDisconnectionSqlCodes(Collection)}, - * but instead of setting codes that signal fatal disconnections, it defines codes that should be ignored - * during such checks. - *

- *

- * Note: This method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@code getConnection, setLogwriter, setLoginTimeout, - * getLoginTimeout, getLogWriter}. - *

- * - * @param disconnectionIgnoreSqlCodes SQL State codes that should be ignored in disconnection checks - * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}. - * @since 2.13.0 - */ - public void setDisconnectionIgnoreSqlCodes(final Collection disconnectionIgnoreSqlCodes) { - Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes); - final Set collect = Utils.isEmpty(disconnectionIgnoreSqlCodes) ? null - : disconnectionIgnoreSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet()); - this.disconnectionIgnoreSqlCodes = Utils.isEmpty(collect) ? null : collect; - } - - /** - * Sets the SQL State codes considered to signal fatal conditions. - *

- * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with - * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #getFastFailValidation()} - * is {@code true}, whenever connections created by this datasource generate exceptions with SQL State codes in this - * list, they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at - * isValid or validation query). - *

- *

- * If {@link #getFastFailValidation()} is {@code false} setting this property has no effect. - *

- *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@code getConnection, setLogwriter, - * setLoginTimeout, getLoginTimeout, getLogWriter}. - *

- * - * @param disconnectionSqlCodes SQL State codes considered to signal fatal conditions - * @since 2.1 - * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}. - */ - public void setDisconnectionSqlCodes(final Collection disconnectionSqlCodes) { - Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes); - final Set collect = Utils.isEmpty(disconnectionSqlCodes) ? null - : disconnectionSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet()); - this.disconnectionSqlCodes = Utils.isEmpty(collect) ? null : collect; - } - - /** - * Sets the JDBC Driver instance to use for this pool. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param driver The JDBC Driver instance to use for this pool. - */ - public synchronized void setDriver(final Driver driver) { - this.driver = driver; - } - - /** - * Sets the class loader to be used to load the JDBC driver. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param driverClassLoader the class loader with which to load the JDBC driver - */ - public synchronized void setDriverClassLoader(final ClassLoader driverClassLoader) { - this.driverClassLoader = driverClassLoader; - } - - /** - * Sets the JDBC driver class name. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param driverClassName the class name of the JDBC driver - */ - public synchronized void setDriverClassName(final String driverClassName) { - this.driverClassName = isEmpty(driverClassName) ? null : driverClassName; - } - - /** - * Sets the {code durationBetweenEvictionRuns} property. - * - * @param timeBetweenEvictionRunsMillis the new time between evictor runs - * @see #setDurationBetweenEvictionRuns(Duration) - * @since 2.10.0 - */ - public synchronized void setDurationBetweenEvictionRuns(final Duration timeBetweenEvictionRunsMillis) { - this.durationBetweenEvictionRuns = timeBetweenEvictionRunsMillis; - setConnectionPool(GenericObjectPool::setDurationBetweenEvictionRuns, timeBetweenEvictionRunsMillis); - } - - /** - * Sets the value of the flag that controls whether or not connections being returned to the pool will be checked - * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit - * setting is {@code false} when the connection is returned. It is {@code true} by default. - * - * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured - * with auto-commit. - * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}. - */ - @Deprecated - public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { - this.autoCommitOnReturn = autoCommitOnReturn; - } - - /** - * Sets the EvictionPolicy implementation to use with this connection pool. - * - * @param evictionPolicyClassName The fully qualified class name of the EvictionPolicy implementation - */ - public synchronized void setEvictionPolicyClassName(final String evictionPolicyClassName) { - setConnectionPool(GenericObjectPool::setEvictionPolicyClassName, evictionPolicyClassName); - this.evictionPolicyClassName = evictionPolicyClassName; - } - - /** - * @see #getFastFailValidation() - * @param fastFailValidation true means connections created by this factory will fast fail validation - * @since 2.1 - */ - public void setFastFailValidation(final boolean fastFailValidation) { - this.fastFailValidation = fastFailValidation; - } - - /** - * Sets the initial size of the connection pool. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param initialSize the number of connections created when the pool is initialized - */ - public synchronized void setInitialSize(final int initialSize) { - this.initialSize = initialSize; - } - - /** - * Sets the JMX name that has been requested for this DataSource. If the requested name is not valid, an alternative - * may be chosen. This DataSource will attempt to register itself using this name. If another component registers - * this DataSource with JMX and this name is valid this name will be used in preference to any specified by the - * other component. - * - * @param jmxName The JMX name that has been requested for this DataSource - */ - public void setJmxName(final String jmxName) { - this.jmxName = jmxName; - } - - /** - * Sets the LIFO property. True means the pool behaves as a LIFO queue; false means FIFO. - * - * @param lifo the new value for the LIFO property - */ - public synchronized void setLifo(final boolean lifo) { - this.lifo = lifo; - setConnectionPool(GenericObjectPool::setLifo, lifo); - } - - /** - * @param logAbandoned new logAbandoned property value - */ - public void setLogAbandoned(final boolean logAbandoned) { - setAbandoned(AbandonedConfig::setLogAbandoned, logAbandoned); - } - - /** - * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or - * not log messages are generated when the pool closes connections due to maximum lifetime exceeded. Set this - * property to false to suppress log messages when connections expire. - * - * @param logExpiredConnections Whether or not log messages are generated when the pool closes connections due to - * maximum lifetime exceeded. - */ - public void setLogExpiredConnections(final boolean logExpiredConnections) { - this.logExpiredConnections = logExpiredConnections; - } - - /** - * BasicDataSource does NOT support this method. - * - *

- * Sets the login timeout (in seconds) for connecting to the database. - *

- *

- * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. - *

- * - * @param loginTimeout The new login timeout, or zero for no timeout - * @throws UnsupportedOperationException If the DataSource implementation does not support the login timeout - * feature. - * @throws SQLException if a database access error occurs - */ - @Override - public void setLoginTimeout(final int loginTimeout) throws SQLException { - // This method isn't supported by the PoolingDataSource returned by the - // createDataSource - throw new UnsupportedOperationException("Not supported by BasicDataSource"); - } - - /** - * Sets the log writer being used by this data source. - *

- * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. - *

- * - * @param logWriter The new log writer - * @throws SQLException if a database access error occurs - */ - @Override - public void setLogWriter(final PrintWriter logWriter) throws SQLException { - createDataSource().setLogWriter(logWriter); - this.logWriter = logWriter; - } - - /** - * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an - * infinite lifetime. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param maxConnDuration The maximum permitted lifetime of a connection. - * @since 2.10.0 - */ - public void setMaxConn(final Duration maxConnDuration) { - this.maxConnDuration = maxConnDuration; - } - - /** - * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an - * infinite lifetime. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds. - * @deprecated Use {@link #setMaxConn(Duration)}. - */ - @Deprecated - public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { - this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis); - } - - /** - * Sets the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed on - * return to the pool. - * - * @see #getMaxIdle() - * @param maxIdle the new value for maxIdle - */ - public synchronized void setMaxIdle(final int maxIdle) { - this.maxIdle = maxIdle; - setConnectionPool(GenericObjectPool::setMaxIdle, maxIdle); - } - - /** - * Sets the value of the {@code maxOpenPreparedStatements} property. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param maxOpenStatements the new maximum number of prepared statements - */ - public synchronized void setMaxOpenPreparedStatements(final int maxOpenStatements) { - this.maxOpenPreparedStatements = maxOpenStatements; - } - - /** - * Sets the maximum total number of idle and borrows connections that can be active at the same time. Use a negative - * value for no limit. - * - * @param maxTotal the new value for maxTotal - * @see #getMaxTotal() - */ - public synchronized void setMaxTotal(final int maxTotal) { - this.maxTotal = maxTotal; - setConnectionPool(GenericObjectPool::setMaxTotal, maxTotal); - } - - /** - * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely. - * - * @param maxWaitDuration the new value for MaxWaitMillis - * @see #getMaxWaitDuration() - * @since 2.10.0 - */ - public synchronized void setMaxWait(final Duration maxWaitDuration) { - this.maxWaitDuration = maxWaitDuration; - setConnectionPool(GenericObjectPool::setMaxWait, maxWaitDuration); - } - - /** - * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely. - * - * @param maxWaitMillis the new value for MaxWaitMillis - * @see #getMaxWaitDuration() - * @deprecated {@link #setMaxWait(Duration)}. - */ - @Deprecated - public synchronized void setMaxWaitMillis(final long maxWaitMillis) { - setMaxWait(Duration.ofMillis(maxWaitMillis)); - } - - /** - * Sets the {code minEvictableIdleDuration} property. - * - * @param minEvictableIdleDuration the minimum amount of time an object may sit idle in the pool - * @see #setMinEvictableIdle(Duration) - * @since 2.10.0 - */ - public synchronized void setMinEvictableIdle(final Duration minEvictableIdleDuration) { - this.minEvictableIdleDuration = minEvictableIdleDuration; - setConnectionPool(GenericObjectPool::setMinEvictableIdleDuration, minEvictableIdleDuration); - } - - /** - * Sets the {code minEvictableIdleDuration} property. - * - * @param minEvictableIdleTimeMillis the minimum amount of time an object may sit idle in the pool - * @see #setMinEvictableIdle(Duration) - * @deprecated Use {@link #setMinEvictableIdle(Duration)}. - */ - @Deprecated - public synchronized void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) { - setMinEvictableIdle(Duration.ofMillis(minEvictableIdleTimeMillis)); - } - - /** - * Sets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections are - * available when the idle object evictor runs. The value of this property has no effect unless - * {code durationBetweenEvictionRuns} has a positive value. - * - * @param minIdle the new value for minIdle - * @see GenericObjectPool#setMinIdle(int) - */ - public synchronized void setMinIdle(final int minIdle) { - this.minIdle = minIdle; - setConnectionPool(GenericObjectPool::setMinIdle, minIdle); - } - - /** - * Sets the value of the {code numTestsPerEvictionRun} property. - * - * @param numTestsPerEvictionRun the new {code numTestsPerEvictionRun} value - * @see #setNumTestsPerEvictionRun(int) - */ - public synchronized void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { - this.numTestsPerEvictionRun = numTestsPerEvictionRun; - setConnectionPool(GenericObjectPool::setNumTestsPerEvictionRun, numTestsPerEvictionRun); - } - - /** - * Sets the {code password}. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param password new value for the password - */ - public void setPassword(final String password) { - this.password = password; - } - - /** - * Sets whether to pool statements or not. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param poolingStatements pooling on or off - */ - public synchronized void setPoolPreparedStatements(final boolean poolingStatements) { - this.poolPreparedStatements = poolingStatements; - } - - /** - * Sets if connection level JMX tracking is requested for this DataSource. If true, each connection will be - * registered for tracking with JMX. - * - * @param registerConnectionMBean connection tracking requested for this DataSource. - */ - public void setRegisterConnectionMBean(final boolean registerConnectionMBean) { - this.registerConnectionMBean = registerConnectionMBean; - } - - /** - * @param removeAbandonedOnBorrow true means abandoned connections may be removed when connections are borrowed from - * the pool. - * @see #getRemoveAbandonedOnBorrow() - */ - public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) { - setAbandoned(AbandonedConfig::setRemoveAbandonedOnBorrow, removeAbandonedOnBorrow); - } - - /** - * @param removeAbandonedOnMaintenance true means abandoned connections may be removed on pool maintenance. - * @see #getRemoveAbandonedOnMaintenance() - */ - public void setRemoveAbandonedOnMaintenance(final boolean removeAbandonedOnMaintenance) { - setAbandoned(AbandonedConfig::setRemoveAbandonedOnMaintenance, removeAbandonedOnMaintenance); - } - - /** - * Sets the timeout before an abandoned connection can be removed. - *

- * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and - * {code getRemoveAbandonedOnMaintenance()} are false. - *

- * - * @param removeAbandonedTimeout new abandoned timeout - * @see #getRemoveAbandonedTimeoutDuration() - * @see #getRemoveAbandonedOnBorrow() - * @see #getRemoveAbandonedOnMaintenance() - * @since 2.10.0 - */ - public void setRemoveAbandonedTimeout(final Duration removeAbandonedTimeout) { - setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, removeAbandonedTimeout); - } - - /** - * Sets the timeout in seconds before an abandoned connection can be removed. - *

- * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and - * {@link #getRemoveAbandonedOnMaintenance()} are false. - *

- * - * @param removeAbandonedTimeout new abandoned timeout in seconds - * @see #getRemoveAbandonedTimeoutDuration() - * @see #getRemoveAbandonedOnBorrow() - * @see #getRemoveAbandonedOnMaintenance() - * @deprecated Use {@link #setRemoveAbandonedTimeout(Duration)}. - */ - @Deprecated - public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) { - setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, Duration.ofSeconds(removeAbandonedTimeout)); - } - - /** - * Sets the flag that controls if a connection will be rolled back when it is returned to the pool if auto commit is - * not enabled and the connection is not read only. - * - * @param rollbackOnReturn whether a connection will be rolled back when it is returned to the pool. - */ - public void setRollbackOnReturn(final boolean rollbackOnReturn) { - this.rollbackOnReturn = rollbackOnReturn; - } - - /** - * Sets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the - * idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. - * - * @param softMinEvictableIdleTimeMillis minimum amount of time a connection may sit idle in the pool before it is - * eligible for eviction, assuming there are minIdle idle connections in the - * pool. - * @see #getSoftMinEvictableIdleTimeMillis - * @since 2.10.0 - */ - public synchronized void setSoftMinEvictableIdle(final Duration softMinEvictableIdleTimeMillis) { - this.softMinEvictableIdleDuration = softMinEvictableIdleTimeMillis; - setConnectionPool(GenericObjectPool::setSoftMinEvictableIdleDuration, softMinEvictableIdleTimeMillis); - } - - /** - * Sets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the - * idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. - * - * @param softMinEvictableIdleTimeMillis minimum amount of time a connection may sit idle in the pool before it is - * eligible for eviction, assuming there are minIdle idle connections in the - * pool. - * @see #getSoftMinEvictableIdleTimeMillis - * @deprecated Use {@link #setSoftMinEvictableIdle(Duration)}. - */ - @Deprecated - public synchronized void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) { - setSoftMinEvictableIdle(Duration.ofMillis(softMinEvictableIdleTimeMillis)); - } - - /** - * Sets the {code testOnBorrow} property. This property determines whether or not the pool will validate objects - * before they are borrowed from the pool. - * - * @param testOnBorrow new value for testOnBorrow property - */ - public synchronized void setTestOnBorrow(final boolean testOnBorrow) { - this.testOnBorrow = testOnBorrow; - setConnectionPool(GenericObjectPool::setTestOnBorrow, testOnBorrow); - } - - /** - * Sets the {code testOnCreate} property. This property determines whether or not the pool will validate objects - * immediately after they are created by the pool - * - * @param testOnCreate new value for testOnCreate property - */ - public synchronized void setTestOnCreate(final boolean testOnCreate) { - this.testOnCreate = testOnCreate; - setConnectionPool(GenericObjectPool::setTestOnCreate, testOnCreate); - } - - /** - * Sets the {@code testOnReturn} property. This property determines whether or not the pool will validate - * objects before they are returned to the pool. - * - * @param testOnReturn new value for testOnReturn property - */ - public synchronized void setTestOnReturn(final boolean testOnReturn) { - this.testOnReturn = testOnReturn; - setConnectionPool(GenericObjectPool::setTestOnReturn, testOnReturn); - } - - /** - * Sets the {@code testWhileIdle} property. This property determines whether or not the idle object evictor - * will validate connections. - * - * @param testWhileIdle new value for testWhileIdle property - */ - public synchronized void setTestWhileIdle(final boolean testWhileIdle) { - this.testWhileIdle = testWhileIdle; - setConnectionPool(GenericObjectPool::setTestWhileIdle, testWhileIdle); - } - - /** - * Sets the {code durationBetweenEvictionRuns} property. - * - * @param timeBetweenEvictionRunsMillis the new time between evictor runs - * @see #setDurationBetweenEvictionRuns(Duration) - * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}. - */ - @Deprecated - public synchronized void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { - setDurationBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis)); - } - - /** - * Sets the {code connection string}. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param connectionString the new value for the JDBC connection connectionString - */ - public synchronized void setUrl(final String connectionString) { - this.connectionString = connectionString; - } - - /** - * Sets the {code userName}. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param userName the new value for the JDBC connection user name - */ - public void setUsername(final String userName) { - this.userName = userName; - } - - /** - * Sets the {code validationQuery}. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param validationQuery the new value for the validation query - */ - public void setValidationQuery(final String validationQuery) { - this.validationQuery = isEmpty(validationQuery) ? null : validationQuery; - } - - /** - * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a - * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param validationQueryTimeoutDuration new validation query timeout value in seconds - * @since 2.10.0 - */ - public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { - this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; - } - - /** - * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a - * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. - *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first - * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, - * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. - *

- * - * @param validationQueryTimeoutSeconds new validation query timeout value in seconds - * @deprecated Use {@link #setValidationQueryTimeout(Duration)}. - */ - @Deprecated - public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { - this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); - } - - /** - * Starts the datasource. - *

- * It is not necessary to call this method before using a newly created BasicDataSource instance, but - * calling it in that context causes the datasource to be immediately initialized (instead of waiting for - * the first {@link #getConnection()} request). Its primary use is to restart and reinitialize a - * datasource that has been closed. - *

- * When this method is called after {@link #close()}, connections checked out by clients - * before the datasource was stopped do not count in {@link #getMaxTotal()} or {@link #getNumActive()}. - * For example, if there are 3 connections checked out by clients when {@link #close()} is invoked and they are - * not returned before {@link #start()} is invoked, after this method is called, {@link #getNumActive()} will - * return 0. These connections will be physically closed when they are returned, but they will not count against - * the maximum allowed in the newly started datasource. - * - * @throws SQLException if an error occurs initializing the datasource - */ - @Override - public synchronized void start() throws SQLException { - closed = false; - createDataSource(); - } - - /** - * Starts the connection pool maintenance task, if configured. - */ - protected void startPoolMaintenance() { - if (connectionPool != null && durationBetweenEvictionRuns.compareTo(Duration.ZERO) > 0) { - connectionPool.setDurationBetweenEvictionRuns(durationBetweenEvictionRuns); - } - } - - private Collector> toLinkedHashSet() { - return Collectors.toCollection(LinkedHashSet::new); - } - - @Override - public T unwrap(final Class iface) throws SQLException { - if (isWrapperFor(iface)) { - return iface.cast(this); - } - throw new SQLException(this + " is not a wrapper for " + iface); - } - - private void updateJmxName(final GenericObjectPoolConfig config) { - if (registeredJmxObjectName == null) { - return; - } - final StringBuilder base = new StringBuilder(registeredJmxObjectName.toString()); - base.append(Constants.JMX_CONNECTION_POOL_BASE_EXT); - config.setJmxNameBase(base.toString()); - config.setJmxNamePrefix(Constants.JMX_CONNECTION_POOL_PREFIX); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.logging.Logger; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.management.MBeanRegistration; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.StandardMBean; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.AbandonedConfig; +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +/** + * Basic implementation of {@code javax.sql.DataSource} that is configured via JavaBeans properties. + *

+ * This is not the only way to combine the commons-dbcp2 and commons-pool2 packages, but provides a + * one-stop solution for basic requirements. + *

+ * + * @since 2.0 + */ +public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBeanRegistration, AutoCloseable { + + private static final Log log = LogFactory.getLog(BasicDataSource.class); + + static { + // Attempt to prevent deadlocks - see DBCP - 272 + DriverManager.getDrivers(); + try { + // Load classes now to prevent AccessControlExceptions later + // A number of classes are loaded when getConnection() is called + // but the following classes are not loaded and therefore require + // explicit loading. + if (Utils.isSecurityEnabled()) { + final ClassLoader loader = BasicDataSource.class.getClassLoader(); + final String dbcpPackageName = BasicDataSource.class.getPackage().getName(); + loader.loadClass(dbcpPackageName + ".DelegatingCallableStatement"); + loader.loadClass(dbcpPackageName + ".DelegatingDatabaseMetaData"); + loader.loadClass(dbcpPackageName + ".DelegatingPreparedStatement"); + loader.loadClass(dbcpPackageName + ".DelegatingResultSet"); + loader.loadClass(dbcpPackageName + ".PoolableCallableStatement"); + loader.loadClass(dbcpPackageName + ".PoolablePreparedStatement"); + loader.loadClass(dbcpPackageName + ".PoolingConnection$StatementType"); + loader.loadClass(dbcpPackageName + ".PStmtKey"); + + final String poolPackageName = PooledObject.class.getPackage().getName(); + loader.loadClass(poolPackageName + ".impl.LinkedBlockingDeque$Node"); + loader.loadClass(poolPackageName + ".impl.GenericKeyedObjectPool$ObjectDeque"); + } + } catch (final ClassNotFoundException cnfe) { + throw new IllegalStateException("Unable to pre-load classes", cnfe); + } + } + + /** + * Validates the given factory. + * + * @param connectionFactory the factory + * @throws SQLException Thrown by one of the factory methods while managing a temporary pooled object. + */ + @SuppressWarnings("resource") + protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) throws SQLException { + PoolableConnection conn = null; + PooledObject p = null; + try { + p = connectionFactory.makeObject(); + conn = p.getObject(); + connectionFactory.activateObject(p); + connectionFactory.validateConnection(conn); + connectionFactory.passivateObject(p); + } finally { + if (p != null) { + connectionFactory.destroyObject(p); + } + } + } + + /** + * The default auto-commit state of connections created by this pool. + */ + private volatile Boolean defaultAutoCommit; + + /** + * The default read-only state of connections created by this pool. + */ + private transient Boolean defaultReadOnly; + + /** + * The default TransactionIsolation state of connections created by this pool. + */ + private volatile int defaultTransactionIsolation = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; + + private Duration defaultQueryTimeoutDuration; + + /** + * The default "catalog" of connections created by this pool. + */ + private volatile String defaultCatalog; + + /** + * The default "schema" of connections created by this pool. + */ + private volatile String defaultSchema; + + /** + * The property that controls if the pooled connections cache some state rather than query the database for current + * state to improve performance. + */ + private boolean cacheState = true; + + /** + * The instance of the JDBC Driver to use. + */ + private Driver driver; + + /** + * The fully qualified Java class name of the JDBC driver to be used. + */ + private String driverClassName; + + /** + * The class loader instance to use to load the JDBC driver. If not specified, {@link Class#forName(String)} is used + * to load the JDBC driver. If specified, {@link Class#forName(String, boolean, ClassLoader)} is used. + */ + private ClassLoader driverClassLoader; + + /** + * True means that borrowObject returns the most recently used ("last in") connection in the pool (if there are idle + * connections available). False means that the pool behaves as a FIFO queue - connections are taken from the idle + * instance pool in the order that they are returned to the pool. + */ + private boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO; + + /** + * The maximum number of active connections that can be allocated from this pool at the same time, or negative for + * no limit. + */ + private int maxTotal = GenericObjectPoolConfig.DEFAULT_MAX_TOTAL; + + /** + * The maximum number of connections that can remain idle in the pool, without extra ones being destroyed, or + * negative for no limit. If maxIdle is set too low on heavily loaded systems it is possible you will see + * connections being closed and almost immediately new connections being opened. This is a result of the active + * threads momentarily closing connections faster than they are opening them, causing the number of idle connections + * to rise above maxIdle. The best value for maxIdle for heavily loaded system will vary but the default is a good + * starting point. + */ + private int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE; + + /** + * The minimum number of active connections that can remain idle in the pool, without extra ones being created when + * the evictor runs, or 0 to create none. The pool attempts to ensure that minIdle connections are available when + * the idle object evictor runs. The value of this property has no effect unless + * {@link #durationBetweenEvictionRuns} has a positive value. + */ + private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE; + + /** + * The initial number of connections that are created when the pool is started. + */ + private int initialSize; + + /** + * The maximum Duration that the pool will wait (when there are no available connections) for a + * connection to be returned before throwing an exception, or <= 0 to wait indefinitely. + */ + private Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT; + + /** + * Prepared statement pooling for this pool. When this property is set to {@code true} both PreparedStatements + * and CallableStatements are pooled. + */ + private boolean poolPreparedStatements; + + private boolean clearStatementPoolOnReturn; + + /** + *

+ * The maximum number of open statements that can be allocated from the statement pool at the same time, or negative + * for no limit. Since a connection usually only uses one or two statements at a time, this is mostly used to help + * detect resource leaks. + *

+ *

+ * Note: As of version 1.3, CallableStatements (those produced by {@link Connection#prepareCall}) are pooled along + * with PreparedStatements (produced by {@link Connection#prepareStatement}) and + * {@code maxOpenPreparedStatements} limits the total number of prepared or callable statements that may be in + * use at a given time. + *

+ */ + private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; + + /** + * The indication of whether objects will be validated as soon as they have been created by the pool. If the object + * fails to validate, the borrow operation that triggered the creation will fail. + */ + private boolean testOnCreate; + + /** + * The indication of whether objects will be validated before being borrowed from the pool. If the object fails to + * validate, it will be dropped from the pool, and we will attempt to borrow another. + */ + private boolean testOnBorrow = true; + + /** + * The indication of whether objects will be validated before being returned to the pool. + */ + private boolean testOnReturn; + + /** + * The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle + * object evictor thread will be run. + */ + private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; + + /** + * The number of objects to examine during each run of the idle object evictor thread (if any). + */ + private int numTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; + + /** + * The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle + * object evictor (if any). + */ + private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; + + /** + * The minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the idle + * object evictor, with the extra condition that at least "minIdle" connections remain in the pool. Note that + * {@code minEvictableIdleTimeMillis} takes precedence over this parameter. See + * {@link #getSoftMinEvictableIdleDuration()}. + */ + private Duration softMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION; + + private String evictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME; + + /** + * The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to + * validate, it will be dropped from the pool. + */ + private boolean testWhileIdle; + + /** + * The connection password to be passed to our JDBC driver to establish a connection. + */ + private volatile String password; + + /** + * The connection string to be passed to our JDBC driver to establish a connection. + */ + private String connectionString; + + /** + * The connection user name to be passed to our JDBC driver to establish a connection. + */ + private String userName; + + /** + * The SQL query that will be used to validate connections from this pool before returning them to the caller. If + * specified, this query MUST be an SQL SELECT statement that returns at least one row. If not + * specified, {@link Connection#isValid(int)} will be used to validate connections. + */ + private volatile String validationQuery; + + /** + * Timeout in seconds before connection validation queries fail. + */ + private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); + + /** + * The fully qualified Java class name of a {@link ConnectionFactory} implementation. + */ + private String connectionFactoryClassName; + + /** + * These SQL statements run once after a Connection is created. + *

+ * This property can be used for example to run ALTER SESSION SET NLS_SORT=XCYECH in an Oracle Database only once + * after connection creation. + *

+ */ + private volatile List connectionInitSqls; + + /** + * Controls access to the underlying connection. + */ + private boolean accessToUnderlyingConnectionAllowed; + + private Duration maxConnDuration = Duration.ofMillis(-1); + + private boolean logExpiredConnections = true; + + private String jmxName; + + private boolean registerConnectionMBean = true; + + private boolean autoCommitOnReturn = true; + + private boolean rollbackOnReturn = true; + + private volatile Set disconnectionSqlCodes; + + /** + * A collection of SQL State codes that are not considered fatal disconnection codes. + * + * @since 2.13.0 + */ + private volatile Set disconnectionIgnoreSqlCodes; + + private boolean fastFailValidation; + + /** + * The object pool that internally manages our connections. + */ + private volatile GenericObjectPool connectionPool; + + /** + * The connection properties that will be sent to our JDBC driver when establishing new connections. + * NOTE - The "user" and "password" properties will be passed explicitly, so they do not need to be + * included here. + */ + private Properties connectionProperties = new Properties(); + + /** + * The data source we will use to manage connections. This object should be acquired ONLY by calls + * to the {@code createDataSource()} method. + */ + private volatile DataSource dataSource; + + /** + * The PrintWriter to which log messages should be directed. + */ + private volatile PrintWriter logWriter = new PrintWriter( + new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); + + private AbandonedConfig abandonedConfig; + + private boolean closed; + + /** + * Actual name under which this component has been registered. + */ + private ObjectNameWrapper registeredJmxObjectName; + + /** + * Adds a custom connection property to the set that will be passed to our JDBC driver. This MUST + * be called before the first connection is retrieved (along with all the other configuration property setters). + * Calls to this method after the connection pool has been initialized have no effect. + * + * @param name Name of the custom connection property + * @param value Value of the custom connection property + */ + public void addConnectionProperty(final String name, final String value) { + connectionProperties.put(name, value); + } + + /** + * Closes and releases all idle connections that are currently stored in the connection pool associated with this + * data source. + *

+ * Connections that are checked out to clients when this method is invoked are not affected. When client + * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the + * underlying JDBC connections are closed. + *

+ *

+ * Attempts to acquire connections using {@link #getConnection()} after this method has been invoked result in + * SQLExceptions. To reopen a datasource that has been closed using this method, use {@link #start()}. + *

+ *

+ * This method is idempotent - i.e., closing an already closed BasicDataSource has no effect and does not generate + * exceptions. + *

+ * + * @throws SQLException if an error occurs closing idle connections + */ + @Override + public synchronized void close() throws SQLException { + if (registeredJmxObjectName != null) { + registeredJmxObjectName.unregisterMBean(); + registeredJmxObjectName = null; + } + closed = true; + final GenericObjectPool oldPool = connectionPool; + connectionPool = null; + dataSource = null; + try { + if (oldPool != null) { + oldPool.close(); + } + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException(Utils.getMessage("pool.close.fail"), e); + } + } + + /** + * Closes the connection pool, silently swallowing any exception that occurs. + */ + private void closeConnectionPool() { + final GenericObjectPool oldPool = connectionPool; + connectionPool = null; + Utils.closeQuietly(oldPool); + } + + /** + * Creates a JDBC connection factory for this data source. The JDBC driver is loaded using the following algorithm: + *
    + *
  1. If a Driver instance has been specified via {@link #setDriver(Driver)} use it
  2. + *
  3. If no Driver instance was specified and {code driverClassName} is specified that class is loaded using the + * {@link ClassLoader} of this class or, if {code driverClassLoader} is set, {code driverClassName} is loaded + * with the specified {@link ClassLoader}.
  4. + *
  5. If {code driverClassName} is specified and the previous attempt fails, the class is loaded using the + * context class loader of the current thread.
  6. + *
  7. If a driver still isn't loaded one is loaded via the {@link DriverManager} using the specified {code connectionString}. + *
+ *

+ * This method exists so subclasses can replace the implementation class. + *

+ * + * @return A new connection factory. + * @throws SQLException If the connection factory cannot be created + */ + protected ConnectionFactory createConnectionFactory() throws SQLException { + // Load the JDBC driver class + return ConnectionFactoryFactory.createConnectionFactory(this, DriverFactory.createDriver(this)); + } + + /** + * Creates a connection pool for this datasource. This method only exists so subclasses can replace the + * implementation class. + *

+ * This implementation configures all pool properties other than timeBetweenEvictionRunsMillis. Setting that + * property is deferred to {@link #startPoolMaintenance()}, since setting timeBetweenEvictionRunsMillis to a + * positive value causes {@link GenericObjectPool}'s eviction timer to be started. + *

+ * + * @param factory The factory to use to create new connections for this pool. + */ + protected void createConnectionPool(final PoolableConnectionFactory factory) { + // Create an object pool to contain our active connections + final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); + updateJmxName(config); + // Disable JMX on the underlying pool if the DS is not registered: + config.setJmxEnabled(registeredJmxObjectName != null); + // Set up usage tracking if enabled + if (getAbandonedUsageTracking() && abandonedConfig != null) { + abandonedConfig.setUseUsageTracking(true); + } + final GenericObjectPool gop = createObjectPool(factory, config, abandonedConfig); + gop.setMaxTotal(maxTotal); + gop.setMaxIdle(maxIdle); + gop.setMinIdle(minIdle); + gop.setMaxWait(maxWaitDuration); + gop.setTestOnCreate(testOnCreate); + gop.setTestOnBorrow(testOnBorrow); + gop.setTestOnReturn(testOnReturn); + gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + gop.setMinEvictableIdleDuration(minEvictableIdleDuration); + gop.setSoftMinEvictableIdleDuration(softMinEvictableIdleDuration); + gop.setTestWhileIdle(testWhileIdle); + gop.setLifo(lifo); + gop.setSwallowedExceptionListener(new SwallowedExceptionLogger(log, logExpiredConnections)); + gop.setEvictionPolicyClassName(evictionPolicyClassName); + factory.setPool(gop); + connectionPool = gop; + } + + /** + * Creates (if necessary) and return the internal data source we are using to manage our connections. + * + * @return The current internal DataSource or a newly created instance if it has not yet been created. + * @throws SQLException if the object pool cannot be created. + */ + protected synchronized DataSource createDataSource() throws SQLException { + if (closed) { + throw new SQLException("Data source is closed"); + } + + // Return the pool if we have already created it + // This is double-checked locking. This is safe since dataSource is + // volatile and the code is targeted at Java 5 onwards. + if (dataSource != null) { + return dataSource; + } + synchronized (this) { + if (dataSource != null) { + return dataSource; + } + jmxRegister(); + + // create factory which returns raw physical connections + final ConnectionFactory driverConnectionFactory = createConnectionFactory(); + + // Set up the poolable connection factory + final PoolableConnectionFactory poolableConnectionFactory; + try { + poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory); + poolableConnectionFactory.setPoolStatements(poolPreparedStatements); + poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); + // create a pool for our connections + createConnectionPool(poolableConnectionFactory); + final DataSource newDataSource = createDataSourceInstance(); + newDataSource.setLogWriter(logWriter); + connectionPool.addObjects(initialSize); + // If timeBetweenEvictionRunsMillis > 0, start the pool's evictor + // task + startPoolMaintenance(); + dataSource = newDataSource; + } catch (final SQLException | RuntimeException se) { + closeConnectionPool(); + throw se; + } catch (final Exception ex) { + closeConnectionPool(); + throw new SQLException("Error creating connection factory", ex); + } + + return dataSource; + } + } + + /** + * Creates the actual data source instance. This method only exists so that subclasses can replace the + * implementation class. + * + * @throws SQLException if unable to create a datasource instance + * @return A new DataSource instance + */ + protected DataSource createDataSourceInstance() throws SQLException { + final PoolingDataSource pds = new PoolingDataSource<>(connectionPool); + pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); + return pds; + } + + /** + * Creates an object pool used to provide pooling support for {@link Connection JDBC connections}. + * + * @param factory the object factory + * @param poolConfig the object pool configuration + * @param abandonedConfig the abandoned objects configuration + * @return a non-null instance + */ + protected GenericObjectPool createObjectPool(final PoolableConnectionFactory factory, + final GenericObjectPoolConfig poolConfig, final AbandonedConfig abandonedConfig) { + final GenericObjectPool gop; + if (abandonedConfig != null && (abandonedConfig.getRemoveAbandonedOnBorrow() + || abandonedConfig.getRemoveAbandonedOnMaintenance())) { + gop = new GenericObjectPool<>(factory, poolConfig, abandonedConfig); + } else { + gop = new GenericObjectPool<>(factory, poolConfig); + } + return gop; + } + + /** + * Creates the PoolableConnectionFactory and attaches it to the connection pool. This method only exists so + * subclasses can replace the default implementation. + * + * @param driverConnectionFactory JDBC connection factory + * @throws SQLException if an error occurs creating the PoolableConnectionFactory + * @return A new PoolableConnectionFactory configured with the current configuration of this BasicDataSource + */ + protected PoolableConnectionFactory createPoolableConnectionFactory(final ConnectionFactory driverConnectionFactory) + throws SQLException { + PoolableConnectionFactory connectionFactory = null; + try { + if (registerConnectionMBean) { + connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, ObjectNameWrapper.unwrap(registeredJmxObjectName)); + } else { + connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, null); + } + connectionFactory.setValidationQuery(validationQuery); + connectionFactory.setValidationQueryTimeout(validationQueryTimeoutDuration); + connectionFactory.setConnectionInitSql(connectionInitSqls); + connectionFactory.setDefaultReadOnly(defaultReadOnly); + connectionFactory.setDefaultAutoCommit(defaultAutoCommit); + connectionFactory.setDefaultTransactionIsolation(defaultTransactionIsolation); + connectionFactory.setDefaultCatalog(defaultCatalog); + connectionFactory.setDefaultSchema(defaultSchema); + connectionFactory.setCacheState(cacheState); + connectionFactory.setPoolStatements(poolPreparedStatements); + connectionFactory.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); + connectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); + connectionFactory.setMaxConn(maxConnDuration); + connectionFactory.setRollbackOnReturn(getRollbackOnReturn()); + connectionFactory.setAutoCommitOnReturn(getAutoCommitOnReturn()); + connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration()); + connectionFactory.setFastFailValidation(fastFailValidation); + connectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes); + connectionFactory.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes); + validateConnectionFactory(connectionFactory); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e); + } + return connectionFactory; + } + + /** + * Manually evicts idle connections + * + * @throws Exception when there is a problem evicting idle objects. + */ + public void evict() throws Exception { + if (connectionPool != null) { + connectionPool.evict(); + } + } + + /** + * Gets the print writer used by this configuration to log information on abandoned objects. + * + * @return The print writer used by this configuration to log information on abandoned objects. + */ + public PrintWriter getAbandonedLogWriter() { + return abandonedConfig == null ? null : abandonedConfig.getLogWriter(); + } + + /** + * If the connection pool implements {@link org.apache.commons.pool2.UsageTracking UsageTracking}, should the + * connection pool record a stack trace every time a method is called on a pooled connection and retain the most + * recent stack trace to aid debugging of abandoned connections? + * + * @return {@code true} if usage tracking is enabled + */ + @Override + public boolean getAbandonedUsageTracking() { + return abandonedConfig != null && abandonedConfig.getUseUsageTracking(); + } + + /** + * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked + * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit + * setting is {@code false} when the connection is returned. It is {@code true} by default. + * + * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit. + */ + public boolean getAutoCommitOnReturn() { + return autoCommitOnReturn; + } + + /** + * Gets the state caching flag. + * + * @return the state caching flag + */ + @Override + public boolean getCacheState() { + return cacheState; + } + + /** + * Creates (if necessary) and return a connection to the database. + * + * @throws SQLException if a database access error occurs + * @return a database connection + */ + @Override + public Connection getConnection() throws SQLException { + if (Utils.isSecurityEnabled()) { + final PrivilegedExceptionAction action = () -> createDataSource().getConnection(); + try { + return AccessController.doPrivileged(action); + } catch (final PrivilegedActionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof SQLException) { + throw (SQLException) cause; + } + throw new SQLException(e); + } + } + return createDataSource().getConnection(); + } + + /** + * BasicDataSource does NOT support this method. + * + * @param user Database user on whose behalf the Connection is being made + * @param pass The database user's password + * @throws UnsupportedOperationException always thrown. + * @throws SQLException if a database access error occurs + * @return nothing - always throws UnsupportedOperationException + */ + @Override + public Connection getConnection(final String user, final String pass) throws SQLException { + // This method isn't supported by the PoolingDataSource returned by the + // createDataSource + throw new UnsupportedOperationException("Not supported by BasicDataSource"); + } + + /** + * Gets the ConnectionFactoryClassName that has been configured for use by this pool. + *

+ * Note: This getter only returns the last value set by a call to {@link #setConnectionFactoryClassName(String)}. + *

+ * + * @return the ConnectionFactoryClassName that has been configured for use by this pool. + * @since 2.7.0 + */ + public String getConnectionFactoryClassName() { + return this.connectionFactoryClassName; + } + + /** + * Gets the list of SQL statements executed when a physical connection is first created. Returns an empty list if + * there are no initialization statements configured. + * + * @return initialization SQL statements + */ + public List getConnectionInitSqls() { + final List result = connectionInitSqls; + return result == null ? Collections.emptyList() : result; + } + + /** + * Provides the same data as {@link #getConnectionInitSqls()} but in an array so it is accessible via JMX. + */ + @Override + public String[] getConnectionInitSqlsAsArray() { + return getConnectionInitSqls().toArray(Utils.EMPTY_STRING_ARRAY); + } + + /** + * Gets the underlying connection pool. + * + * @return the underlying connection pool. + * @since 2.10.0 + */ + public GenericObjectPool getConnectionPool() { + return connectionPool; + } + + Properties getConnectionProperties() { + return connectionProperties; + } + + /** + * Gets the default auto-commit property. + * + * @return true if default auto-commit is enabled + */ + @Override + public Boolean getDefaultAutoCommit() { + return defaultAutoCommit; + } + + /** + * Gets the default catalog. + * + * @return the default catalog + */ + @Override + public String getDefaultCatalog() { + return this.defaultCatalog; + } + + /** + * Gets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this + * connection. {@code null} means that the driver default will be used. + * + * @return The default query timeout in seconds. + * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. + */ + @Deprecated + public Integer getDefaultQueryTimeout() { + return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); + } + + /** + * Gets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this + * connection. {@code null} means that the driver default will be used. + * + * @return The default query timeout Duration. + * @since 2.10.0 + */ + public Duration getDefaultQueryTimeoutDuration() { + return defaultQueryTimeoutDuration; + } + + /** + * Gets the default readOnly property. + * + * @return true if connections are readOnly by default + */ + @Override + public Boolean getDefaultReadOnly() { + return defaultReadOnly; + } + + /** + * Gets the default schema. + * + * @return the default schema. + * @since 2.5.0 + */ + @Override + public String getDefaultSchema() { + return this.defaultSchema; + } + + /** + * Gets the default transaction isolation state of returned connections. + * + * @return the default value for transaction isolation state + * @see Connection#getTransactionIsolation + */ + @Override + public int getDefaultTransactionIsolation() { + return this.defaultTransactionIsolation; + } + + /** + * Gets the set of SQL State codes that are not considered fatal disconnection codes. + *

+ * This method returns the set of SQL State codes that have been specified to be ignored + * when determining if a {@link SQLException} signals a disconnection. These codes will not + * trigger a disconnection even if they match other disconnection criteria. + *

+ * + * @return a set of SQL State codes that should be ignored for disconnection checks, or an empty set if none have been specified. + * @since 2.13.0 + */ + public Set getDisconnectionIgnoreSqlCodes() { + final Set result = disconnectionIgnoreSqlCodes; + return result == null ? Collections.emptySet() : result; + } + + /** + * Provides the same data as {@link #getDisconnectionIgnoreSqlCodes()} but in an array, so it is accessible via JMX. + * + * @since 2.13.0 + */ + @Override + public String[] getDisconnectionIgnoreSqlCodesAsArray() { + return getDisconnectionIgnoreSqlCodes().toArray(Utils.EMPTY_STRING_ARRAY); + } + + /** + * Gets the set of SQL State codes considered to signal fatal conditions. + * + * @return fatal disconnection state codes + * @see #setDisconnectionSqlCodes(Collection) + * @since 2.1 + */ + public Set getDisconnectionSqlCodes() { + final Set result = disconnectionSqlCodes; + return result == null ? Collections.emptySet() : result; + } + + /** + * Provides the same data as {@link #getDisconnectionSqlCodes} but in an array so it is accessible via JMX. + * + * @since 2.1 + */ + @Override + public String[] getDisconnectionSqlCodesAsArray() { + return getDisconnectionSqlCodes().toArray(Utils.EMPTY_STRING_ARRAY); + } + + /** + * Gets the JDBC Driver that has been configured for use by this pool. + *

+ * Note: This getter only returns the last value set by a call to {@link #setDriver(Driver)}. It does not return any + * driver instance that may have been created from the value set via {@link #setDriverClassName(String)}. + *

+ * + * @return the JDBC Driver that has been configured for use by this pool + */ + public synchronized Driver getDriver() { + return driver; + } + + /** + * Gets the class loader specified for loading the JDBC driver. Returns {@code null} if no class loader has + * been explicitly specified. + *

+ * Note: This getter only returns the last value set by a call to {@link #setDriverClassLoader(ClassLoader)}. It + * does not return the class loader of any driver that may have been set via {@link #setDriver(Driver)}. + *

+ * + * @return The class loader specified for loading the JDBC driver. + */ + public synchronized ClassLoader getDriverClassLoader() { + return this.driverClassLoader; + } + + /** + * Gets the JDBC driver class name. + *

+ * Note: This getter only returns the last value set by a call to {@link #setDriverClassName(String)}. It does not + * return the class name of any driver that may have been set via {@link #setDriver(Driver)}. + *

+ * + * @return the JDBC driver class name + */ + @Override + public synchronized String getDriverClassName() { + return this.driverClassName; + } + + /** + * Gets the value of the {code durationBetweenEvictionRuns} property. + * + * @return the time (in milliseconds) between evictor runs + * @see #setDurationBetweenEvictionRuns(Duration) + * @since 2.10.0 + */ + public synchronized Duration getDurationBetweenEvictionRuns() { + return this.durationBetweenEvictionRuns; + } + + /** + * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked + * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit + * setting is {@code false} when the connection is returned. It is {@code true} by default. + * + * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit. + * @deprecated Use {@link #getAutoCommitOnReturn()}. + */ + @Deprecated + public boolean getEnableAutoCommitOnReturn() { + return autoCommitOnReturn; + } + + /** + * Gets the EvictionPolicy implementation in use with this connection pool. + * + * @return The EvictionPolicy implementation in use with this connection pool. + */ + public synchronized String getEvictionPolicyClassName() { + return evictionPolicyClassName; + } + + /** + * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with + * SQL State indicating fatal disconnection errors. + * + * @return true if connections created by this datasource will fast fail validation. + * @see #setDisconnectionSqlCodes(Collection) + * @see #setDisconnectionIgnoreSqlCodes(Collection) + * @since 2.1 + */ + @Override + public boolean getFastFailValidation() { + return fastFailValidation; + } + + /** + * Gets the initial size of the connection pool. + * + * @return the number of connections created when the pool is initialized + */ + @Override + public synchronized int getInitialSize() { + return this.initialSize; + } + + /** + * Gets the JMX name that has been requested for this DataSource. If the requested name is not valid, an + * alternative may be chosen. + * + * @return The JMX name that has been requested for this DataSource. + */ + public String getJmxName() { + return jmxName; + } + + /** + * Gets the LIFO property. + * + * @return true if connection pool behaves as a LIFO queue. + */ + @Override + public synchronized boolean getLifo() { + return this.lifo; + } + + /** + * Flag to log stack traces for application code which abandoned a Statement or Connection. + *

+ * Defaults to false. + *

+ *

+ * Logging of abandoned Statements and Connections adds overhead for every Connection open or new Statement because + * a stack trace has to be generated. + *

+ */ + @Override + public boolean getLogAbandoned() { + return abandonedConfig != null && abandonedConfig.getLogAbandoned(); + } + + /** + * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or + * not log messages are generated when the pool closes connections due to maximum lifetime exceeded. + * + * @since 2.1 + */ + @Override + public boolean getLogExpiredConnections() { + return logExpiredConnections; + } + + /** + * BasicDataSource does NOT support this method. + * + *

+ * Gets the login timeout (in seconds) for connecting to the database. + *

+ *

+ * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. + *

+ * + * @throws SQLException if a database access error occurs + * @throws UnsupportedOperationException If the DataSource implementation does not support the login timeout + * feature. + * @return login timeout in seconds + */ + @Override + public int getLoginTimeout() throws SQLException { + // This method isn't supported by the PoolingDataSource returned by the createDataSource + throw new UnsupportedOperationException("Not supported by BasicDataSource"); + } + + /** + * Gets the log writer being used by this data source. + *

+ * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. + *

+ * + * @throws SQLException if a database access error occurs + * @return log writer in use + */ + @Override + public PrintWriter getLogWriter() throws SQLException { + return createDataSource().getLogWriter(); + } + + /** + * Gets the maximum permitted duration of a connection. A value of zero or less indicates an + * infinite lifetime. + * @return the maximum permitted duration of a connection. + * @since 2.10.0 + */ + public Duration getMaxConnDuration() { + return maxConnDuration; + } + + /** + * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an + * infinite lifetime. + * @deprecated Use {@link #getMaxConnDuration()}. + */ + @Override + @Deprecated + public long getMaxConnLifetimeMillis() { + return maxConnDuration.toMillis(); + } + + /** + * Gets the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed + * on return to the pool. + *

+ * A negative value indicates that there is no limit + *

+ * + * @return the maximum number of idle connections + */ + @Override + public synchronized int getMaxIdle() { + return this.maxIdle; + } + + /** + * Gets the value of the {@code maxOpenPreparedStatements} property. + * + * @return the maximum number of open statements + */ + @Override + public synchronized int getMaxOpenPreparedStatements() { + return this.maxOpenPreparedStatements; + } + + /** + * Gets the maximum number of active connections that can be allocated at the same time. + *

+ * A negative number means that there is no limit. + *

+ * + * @return the maximum number of active connections + */ + @Override + public synchronized int getMaxTotal() { + return this.maxTotal; + } + + /** + * Gets the maximum Duration that the pool will wait for a connection to be returned before throwing an exception. A + * value less than or equal to zero means the pool is set to wait indefinitely. + * + * @return the maxWaitDuration property value. + * @since 2.10.0 + */ + public synchronized Duration getMaxWaitDuration() { + return this.maxWaitDuration; + } + + /** + * Gets the maximum number of milliseconds that the pool will wait for a connection to be returned before + * throwing an exception. A value less than or equal to zero means the pool is set to wait indefinitely. + * + * @return the maxWaitMillis property value. + * @deprecated Use {@link #getMaxWaitDuration()}. + */ + @Deprecated + @Override + public synchronized long getMaxWaitMillis() { + return this.maxWaitDuration.toMillis(); + } + + /** + * Gets the {code minEvictableIdleDuration} property. + * + * @return the value of the {code minEvictableIdleDuration} property + * @see #setMinEvictableIdle(Duration) + * @since 2.10.0 + */ + public synchronized Duration getMinEvictableIdleDuration() { + return this.minEvictableIdleDuration; + } + + /** + * Gets the {code minEvictableIdleDuration} property. + * + * @return the value of the {code minEvictableIdleDuration} property + * @see #setMinEvictableIdle(Duration) + * @deprecated Use {@link #getMinEvictableIdleDuration()}. + */ + @Deprecated + @Override + public synchronized long getMinEvictableIdleTimeMillis() { + return this.minEvictableIdleDuration.toMillis(); + } + + /** + * Gets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections + * are available when the idle object evictor runs. The value of this property has no effect unless + * {code durationBetweenEvictionRuns} has a positive value. + * + * @return the minimum number of idle connections + * @see GenericObjectPool#getMinIdle() + */ + @Override + public synchronized int getMinIdle() { + return this.minIdle; + } + + /** + * [Read Only] The current number of active connections that have been allocated from this data source. + * + * @return the current number of active connections + */ + @Override + public int getNumActive() { + // Copy reference to avoid NPE if close happens after null check + final GenericObjectPool pool = connectionPool; + return pool == null ? 0 : pool.getNumActive(); + } + + /** + * [Read Only] The current number of idle connections that are waiting to be allocated from this data source. + * + * @return the current number of idle connections + */ + @Override + public int getNumIdle() { + // Copy reference to avoid NPE if close happens after null check + final GenericObjectPool pool = connectionPool; + return pool == null ? 0 : pool.getNumIdle(); + } + + /** + * Gets the value of the {code numTestsPerEvictionRun} property. + * + * @return the number of objects to examine during idle object evictor runs + * @see #setNumTestsPerEvictionRun(int) + */ + @Override + public synchronized int getNumTestsPerEvictionRun() { + return this.numTestsPerEvictionRun; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Gets the password passed to the JDBC driver to establish connections. + * + * @return the connection password + * @deprecated Exposing passwords via JMX is an Information Exposure issue. + */ + @Deprecated + @Override + public String getPassword() { + return this.password; + } + + /** + * Gets the registered JMX ObjectName. + * + * @return the registered JMX ObjectName. + */ + protected ObjectName getRegisteredJmxName() { + return ObjectNameWrapper.unwrap(registeredJmxObjectName); + } + + /** + * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout when borrowObject is invoked. + *

+ * The default value is false. + *

+ *

+ * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more + * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds. + *

+ *

+ * Abandoned connections are identified and removed when {@link #getConnection()} is invoked and all of the + * following conditions hold: + *

+ *
    + *
  • {@link #getRemoveAbandonedOnBorrow()}
  • + *
  • {@link #getNumActive()} > {@link #getMaxTotal()} - 3
  • + *
  • {@link #getNumIdle()} < 2
  • + *
+ * + * @see #getRemoveAbandonedTimeoutDuration() + */ + @Override + public boolean getRemoveAbandonedOnBorrow() { + return abandonedConfig != null && abandonedConfig.getRemoveAbandonedOnBorrow(); + } + + /** + * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout during pool maintenance. + *

+ * The default value is false. + *

+ *

+ * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more + * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds. + *

+ * + * @see #getRemoveAbandonedTimeoutDuration() + */ + @Override + public boolean getRemoveAbandonedOnMaintenance() { + return abandonedConfig != null && abandonedConfig.getRemoveAbandonedOnMaintenance(); + } + + /** + * Gets the timeout in seconds before an abandoned connection can be removed. + *

+ * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one + * of the execute methods) resets the lastUsed property of the parent connection. + *

+ *

+ * Abandoned connection cleanup happens when: + *

+ *
    + *
  • {@link #getRemoveAbandonedOnBorrow()} or {@link #getRemoveAbandonedOnMaintenance()} = true
  • + *
  • {@link #getNumIdle() numIdle} < 2
  • + *
  • {@link #getNumActive() numActive} > {@link #getMaxTotal() maxTotal} - 3
  • + *
+ *

+ * The default value is 300 seconds. + *

+ * @deprecated Use {@link #getRemoveAbandonedTimeoutDuration()}. + */ + @Deprecated + @Override + public int getRemoveAbandonedTimeout() { + return (int) getRemoveAbandonedTimeoutDuration().getSeconds(); + } + + /** + * Gets the timeout before an abandoned connection can be removed. + *

+ * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one + * of the execute methods) resets the lastUsed property of the parent connection. + *

+ *

+ * Abandoned connection cleanup happens when: + *

+ *
    + *
  • {@link #getRemoveAbandonedOnBorrow()} or {@link #getRemoveAbandonedOnMaintenance()} = true
  • + *
  • {@link #getNumIdle() numIdle} < 2
  • + *
  • {@link #getNumActive() numActive} > {@link #getMaxTotal() maxTotal} - 3
  • + *
+ *

+ * The default value is 300 seconds. + *

+ * @return Timeout before an abandoned connection can be removed. + * @since 2.10.0 + */ + public Duration getRemoveAbandonedTimeoutDuration() { + return abandonedConfig == null ? Duration.ofSeconds(300) : abandonedConfig.getRemoveAbandonedTimeoutDuration(); + } + + /** + * Gets the current value of the flag that controls whether a connection will be rolled back when it is returned to + * the pool if auto commit is not enabled and the connection is not read only. + * + * @return whether a connection will be rolled back when it is returned to the pool. + */ + public boolean getRollbackOnReturn() { + return rollbackOnReturn; + } + + /** + * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by + * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. + *

+ * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value, + * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are + * visited by the evictor, idle time is first compared against {@code minEvictableIdleTimeMillis} (without + * considering the number of idle connections in the pool) and then against {@code softMinEvictableIdleTimeMillis}, + * including the {@code minIdle}, constraint. + *

+ * + * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming + * there are minIdle idle connections in the pool + * @since 2.10.0 + */ + public synchronized Duration getSoftMinEvictableIdleDuration() { + return softMinEvictableIdleDuration; + } + + /** + * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by + * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. + *

+ * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value, + * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are + * visited by the evictor, idle time is first compared against {@code minEvictableIdleTimeMillis} (without + * considering the number of idle connections in the pool) and then against {@code softMinEvictableIdleTimeMillis}, + * including the {@code minIdle}, constraint. + *

+ * + * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming + * there are minIdle idle connections in the pool + * @deprecated Use {@link #getSoftMinEvictableIdleDuration()}. + */ + @Deprecated + @Override + public synchronized long getSoftMinEvictableIdleTimeMillis() { + return softMinEvictableIdleDuration.toMillis(); + } + + /** + * Gets the {code testOnBorrow} property. + * + * @return true if objects are validated before being borrowed from the pool + * @see #setTestOnBorrow(boolean) + */ + @Override + public synchronized boolean getTestOnBorrow() { + return this.testOnBorrow; + } + + /** + * Gets the {code testOnCreate} property. + * + * @return true if objects are validated immediately after they are created by the pool + * @see #setTestOnCreate(boolean) + */ + @Override + public synchronized boolean getTestOnCreate() { + return this.testOnCreate; + } + + /** + * Gets the value of the {code testOnReturn} property. + * + * @return true if objects are validated before being returned to the pool + * @see #setTestOnReturn(boolean) + */ + public synchronized boolean getTestOnReturn() { + return this.testOnReturn; + } + + /** + * Gets the value of the {code testWhileIdle} property. + * + * @return true if objects examined by the idle object evictor are validated + * @see #setTestWhileIdle(boolean) + */ + @Override + public synchronized boolean getTestWhileIdle() { + return this.testWhileIdle; + } + + /** + * Gets the value of the {code durationBetweenEvictionRuns} property. + * + * @return the time (in milliseconds) between evictor runs + * @see #setDurationBetweenEvictionRuns(Duration) + * @deprecated Use {@link #getDurationBetweenEvictionRuns()}. + */ + @Deprecated + @Override + public synchronized long getTimeBetweenEvictionRunsMillis() { + return this.durationBetweenEvictionRuns.toMillis(); + } + + /** + * Gets the JDBC connection {code connectionString} property. + * + * @return the {code connectionString} passed to the JDBC driver to establish connections + */ + @Override + public synchronized String getUrl() { + return this.connectionString; + } + + /** + * Gets the JDBC connection {code userName} property. + * + * @return the {code userName} passed to the JDBC driver to establish connections + * @deprecated Use {@link #getUserName()}. + */ + @Deprecated + @Override + public String getUsername() { + return this.userName; + } + + /** + * Gets the validation query used to validate connections before returning them. + * + * @return the SQL validation query + * @see #setValidationQuery(String) + */ + @Override + public String getValidationQuery() { + return this.validationQuery; + } + + /** + * Gets the validation query timeout. + * + * @return the timeout in seconds before connection validation queries fail. + * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. + */ + @Deprecated + @Override + public int getValidationQueryTimeout() { + return (int) validationQueryTimeoutDuration.getSeconds(); + } + + /** + * Gets the validation query timeout. + * + * @return the timeout in seconds before connection validation queries fail. + */ + public Duration getValidationQueryTimeoutDuration() { + return validationQueryTimeoutDuration; + } + + /** + * Manually invalidates a connection, effectively requesting the pool to try to close it, remove it from the pool + * and reclaim pool capacity. + * + * @param connection The Connection to invalidate. + * @throws IllegalStateException if invalidating the connection failed. + * @since 2.1 + */ + @SuppressWarnings("resource") + public void invalidateConnection(final Connection connection) throws IllegalStateException { + if (connection == null) { + return; + } + if (connectionPool == null) { + throw new IllegalStateException("Cannot invalidate connection: ConnectionPool is null."); + } + + final PoolableConnection poolableConnection; + try { + poolableConnection = connection.unwrap(PoolableConnection.class); + if (poolableConnection == null) { + throw new IllegalStateException( + "Cannot invalidate connection: Connection is not a poolable connection."); + } + } catch (final SQLException e) { + throw new IllegalStateException("Cannot invalidate connection: Unwrapping poolable connection failed.", e); + } + + try { + connectionPool.invalidateObject(poolableConnection); + } catch (final Exception e) { + throw new IllegalStateException("Invalidating connection threw unexpected exception", e); + } + } + + /** + * Gets the value of the accessToUnderlyingConnectionAllowed property. + * + * @return true if access to the underlying connection is allowed, false otherwise. + */ + @Override + public synchronized boolean isAccessToUnderlyingConnectionAllowed() { + return this.accessToUnderlyingConnectionAllowed; + } + + /** + * Returns true if the statement pool is cleared when the connection is returned to its pool. + * + * @return true if the statement pool is cleared at connection return + * @since 2.8.0 + */ + @Override + public boolean isClearStatementPoolOnReturn() { + return clearStatementPoolOnReturn; + } + + /** + * If true, this data source is closed and no more connections can be retrieved from this data source. + * + * @return true, if the data source is closed; false otherwise + */ + @Override + public synchronized boolean isClosed() { + return closed; + } + + /** + * Delegates in a null-safe manner to {@link String#isEmpty()}. + * + * @param value the string to test, may be null. + * @return boolean false if value is null, otherwise {@link String#isEmpty()}. + */ + private boolean isEmpty(final String value) { + return value == null || value.trim().isEmpty(); + } + + /** + * Returns true if we are pooling statements. + * + * @return true if prepared and callable statements are pooled + */ + @Override + public synchronized boolean isPoolPreparedStatements() { + return this.poolPreparedStatements; + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + return iface != null && iface.isInstance(this); + } + + private void jmxRegister() { + // Return immediately if this DataSource has already been registered + if (registeredJmxObjectName != null) { + return; + } + // Return immediately if no JMX name has been specified + final String requestedName = getJmxName(); + if (requestedName == null) { + return; + } + registeredJmxObjectName = registerJmxObjectName(requestedName, null); + try { + final StandardMBean standardMBean = new StandardMBean(this, DataSourceMXBean.class); + registeredJmxObjectName.registerMBean(standardMBean); + } catch (final NotCompliantMBeanException e) { + log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored."); + } + } + + /** + * Logs the given message. + * + * @param message the message to log. + */ + protected void log(final String message) { + if (logWriter != null) { + logWriter.println(message); + } + } + + /** + * Logs the given message and throwable. + * + * @param message value to be log. + * @param throwable the throwable. + * @since 2.7.0 + */ + protected void log(final String message, final Throwable throwable) { + if (logWriter != null) { + logWriter.println(message); + throwable.printStackTrace(logWriter); + } + } + + @Override + public void postDeregister() { + // NO-OP + } + + @Override + public void postRegister(final Boolean registrationDone) { + // NO-OP + } + + @Override + public void preDeregister() throws Exception { + // NO-OP + } + + @Override + public ObjectName preRegister(final MBeanServer server, final ObjectName objectName) { + registeredJmxObjectName = registerJmxObjectName(getJmxName(), objectName); + return ObjectNameWrapper.unwrap(registeredJmxObjectName); + } + + private ObjectNameWrapper registerJmxObjectName(final String requestedName, final ObjectName objectName) { + ObjectNameWrapper objectNameWrapper = null; + if (requestedName != null) { + try { + objectNameWrapper = ObjectNameWrapper.wrap(requestedName); + } catch (final MalformedObjectNameException e) { + log.warn("The requested JMX name '" + requestedName + "' was not valid and will be ignored."); + } + } + if (objectNameWrapper == null) { + objectNameWrapper = ObjectNameWrapper.wrap(objectName); + } + return objectNameWrapper; + } + + /** + * Removes a custom connection property. + * + * @param name Name of the custom connection property to remove + * @see #addConnectionProperty(String, String) + */ + public void removeConnectionProperty(final String name) { + connectionProperties.remove(name); + } + + /** + * Restarts the datasource. + *

+ * This method calls {@link #close()} and {@link #start()} in sequence within synchronized scope so any + * connection requests that come in while the datasource is shutting down will be served by the new pool. + *

+ * Idle connections that are stored in the connection pool when this method is invoked are closed, but + * connections that are checked out to clients when this method is invoked are not affected. When client + * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the + * underlying JDBC connections are closed. These connections do not count in {@link #getMaxTotal()} or + * {@link #getNumActive()} after invoking this method. For example, if there are 3 connections checked out by + * clients when {@link #restart()} is invoked, after this method is called, {@link #getNumActive()} will + * return 0 and up to {@link #getMaxTotal()} + 3 connections may be open until the connections sourced from + * the original pool are returned. + *

+ * The new connection pool created by this method is initialized with currently set configuration properties. + * + * @throws SQLException if an error occurs initializing the datasource + */ + @Override + public synchronized void restart() throws SQLException { + close(); + start(); + } + + private void setAbandoned(final BiConsumer consumer, final T object) { + if (abandonedConfig == null) { + abandonedConfig = new AbandonedConfig(); + } + consumer.accept(abandonedConfig, object); + final GenericObjectPool gop = this.connectionPool; + if (gop != null) { + gop.setAbandonedConfig(abandonedConfig); + } + } + + /** + * Sets the print writer to be used by this configuration to log information on abandoned objects. + * + * @param logWriter The new log writer + */ + public void setAbandonedLogWriter(final PrintWriter logWriter) { + setAbandoned(AbandonedConfig::setLogWriter, logWriter); + } + + /** + * If the connection pool implements {@link org.apache.commons.pool2.UsageTracking UsageTracking}, configure whether + * the connection pool should record a stack trace every time a method is called on a pooled connection and retain + * the most recent stack trace to aid debugging of abandoned connections. + * + * @param usageTracking A value of {@code true} will enable the recording of a stack trace on every use of a + * pooled connection + */ + public void setAbandonedUsageTracking(final boolean usageTracking) { + setAbandoned(AbandonedConfig::setUseUsageTracking, usageTracking); + } + + /** + * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to + * the underlying connection. (Default: false) + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param allow Access to the underlying connection is granted when true. + */ + public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { + this.accessToUnderlyingConnectionAllowed = allow; + } + + /** + * Sets the value of the flag that controls whether or not connections being returned to the pool will be checked + * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit + * setting is {@code false} when the connection is returned. It is {@code true} by default. + * + * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured + * with auto-commit. + * @since 2.6.0 + */ + public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { + this.autoCommitOnReturn = autoCommitOnReturn; + } + + /** + * Sets the state caching flag. + * + * @param cacheState The new value for the state caching flag + */ + public void setCacheState(final boolean cacheState) { + this.cacheState = cacheState; + } + + /** + * Sets whether the pool of statements (which was enabled with {@link #setPoolPreparedStatements(boolean)}) should + * be cleared when the connection is returned to its pool. Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; + } + + /** + * Sets the ConnectionFactory class name. + * + * @param connectionFactoryClassName A class name. + * @since 2.7.0 + */ + public void setConnectionFactoryClassName(final String connectionFactoryClassName) { + this.connectionFactoryClassName = isEmpty(connectionFactoryClassName) ? null : connectionFactoryClassName; + } + + /** + * Sets the collection of SQL statements to be executed when a physical connection is first created. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param connectionInitSqls Collection of SQL statements to execute on connection creation + */ + public void setConnectionInitSqls(final Collection connectionInitSqls) { + final List collect = Utils.isEmpty(connectionInitSqls) ? null + : connectionInitSqls.stream().filter(s -> !isEmpty(s)).collect(Collectors.toList()); + this.connectionInitSqls = Utils.isEmpty(collect) ? null : collect; + } + + /** + * Sets the list of SQL statements to be executed when a physical connection is first created. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param connectionInitSqls List of SQL statements to execute on connection creation + * @since 2.12.0 + */ + public void setConnectionInitSqls(final List connectionInitSqls) { + setConnectionInitSqls((Collection) connectionInitSqls); + } + + private void setConnectionPool(final BiConsumer, T> consumer, final T object) { + if (connectionPool != null) { + consumer.accept(connectionPool, object); + } + } + + /** + * Sets the connection properties passed to driver.connect(...). + *

+ * Format of the string must be [propertyName=property;]* + *

+ *

+ * NOTE - The "user" and "password" properties will be added explicitly, so they do not need to be included here. + *

+ * + * @param connectionProperties the connection properties used to create new connections + */ + public void setConnectionProperties(final String connectionProperties) { + Objects.requireNonNull(connectionProperties, "connectionProperties"); + final String[] entries = connectionProperties.split(";"); + final Properties properties = new Properties(); + Stream.of(entries).filter(e -> !e.isEmpty()).forEach(entry -> { + final int index = entry.indexOf('='); + if (index > 0) { + final String name = entry.substring(0, index); + final String value = entry.substring(index + 1); + properties.setProperty(name, value); + } else { + // no value is empty string which is how + // java.util.Properties works + properties.setProperty(entry, ""); + } + }); + this.connectionProperties = properties; + } + + /** + * Sets default auto-commit state of connections returned by this datasource. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param defaultAutoCommit default auto-commit value + */ + public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { + this.defaultAutoCommit = defaultAutoCommit; + } + + /** + * Sets the default catalog. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param defaultCatalog the default catalog + */ + public void setDefaultCatalog(final String defaultCatalog) { + this.defaultCatalog = isEmpty(defaultCatalog) ? null : defaultCatalog; + } + + /** + * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this + * connection. {@code null} means that the driver default will be used. + * + * @param defaultQueryTimeoutDuration The default query timeout Duration. + * @since 2.10.0 + */ + public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; + } + + /** + * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this + * connection. {@code null} means that the driver default will be used. + * + * @param defaultQueryTimeoutSeconds The default query timeout in seconds. + * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. + */ + @Deprecated + public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); + } + + /** + * Sets defaultReadonly property. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param defaultReadOnly default read-only value + */ + public void setDefaultReadOnly(final Boolean defaultReadOnly) { + this.defaultReadOnly = defaultReadOnly; + } + + /** + * Sets the default schema. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param defaultSchema the default catalog + * @since 2.5.0 + */ + public void setDefaultSchema(final String defaultSchema) { + this.defaultSchema = isEmpty(defaultSchema) ? null : defaultSchema; + } + + /** + * Sets the default transaction isolation state for returned connections. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param defaultTransactionIsolation the default transaction isolation state + * @see Connection#getTransactionIsolation + */ + public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { + this.defaultTransactionIsolation = defaultTransactionIsolation; + } + + /** + * Sets the SQL State codes that should be ignored when determining fatal disconnection conditions. + *

+ * This method allows you to specify a collection of SQL State codes that will be excluded from + * disconnection checks. These codes will not trigger the "fatally disconnected" status even if they + * match the typical disconnection criteria. This can be useful in scenarios where certain SQL State + * codes (e.g., specific codes starting with "08") are known to be non-fatal in your environment. + *

+ *

+ * The effect of this method is similar to the one described in {@link #setDisconnectionSqlCodes(Collection)}, + * but instead of setting codes that signal fatal disconnections, it defines codes that should be ignored + * during such checks. + *

+ *

+ * Note: This method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@code getConnection, setLogwriter, setLoginTimeout, + * getLoginTimeout, getLogWriter}. + *

+ * + * @param disconnectionIgnoreSqlCodes SQL State codes that should be ignored in disconnection checks + * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}. + * @since 2.13.0 + */ + public void setDisconnectionIgnoreSqlCodes(final Collection disconnectionIgnoreSqlCodes) { + Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes); + final Set collect = Utils.isEmpty(disconnectionIgnoreSqlCodes) ? null + : disconnectionIgnoreSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet()); + this.disconnectionIgnoreSqlCodes = Utils.isEmpty(collect) ? null : collect; + } + + /** + * Sets the SQL State codes considered to signal fatal conditions. + *

+ * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with + * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #getFastFailValidation()} + * is {@code true}, whenever connections created by this datasource generate exceptions with SQL State codes in this + * list, they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at + * isValid or validation query). + *

+ *

+ * If {@link #getFastFailValidation()} is {@code false} setting this property has no effect. + *

+ *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@code getConnection, setLogwriter, + * setLoginTimeout, getLoginTimeout, getLogWriter}. + *

+ * + * @param disconnectionSqlCodes SQL State codes considered to signal fatal conditions + * @since 2.1 + * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}. + */ + public void setDisconnectionSqlCodes(final Collection disconnectionSqlCodes) { + Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes); + final Set collect = Utils.isEmpty(disconnectionSqlCodes) ? null + : disconnectionSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet()); + this.disconnectionSqlCodes = Utils.isEmpty(collect) ? null : collect; + } + + /** + * Sets the JDBC Driver instance to use for this pool. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param driver The JDBC Driver instance to use for this pool. + */ + public synchronized void setDriver(final Driver driver) { + this.driver = driver; + } + + /** + * Sets the class loader to be used to load the JDBC driver. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param driverClassLoader the class loader with which to load the JDBC driver + */ + public synchronized void setDriverClassLoader(final ClassLoader driverClassLoader) { + this.driverClassLoader = driverClassLoader; + } + + /** + * Sets the JDBC driver class name. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param driverClassName the class name of the JDBC driver + */ + public synchronized void setDriverClassName(final String driverClassName) { + this.driverClassName = isEmpty(driverClassName) ? null : driverClassName; + } + + /** + * Sets the {code durationBetweenEvictionRuns} property. + * + * @param timeBetweenEvictionRunsMillis the new time between evictor runs + * @see #setDurationBetweenEvictionRuns(Duration) + * @since 2.10.0 + */ + public synchronized void setDurationBetweenEvictionRuns(final Duration timeBetweenEvictionRunsMillis) { + this.durationBetweenEvictionRuns = timeBetweenEvictionRunsMillis; + setConnectionPool(GenericObjectPool::setDurationBetweenEvictionRuns, timeBetweenEvictionRunsMillis); + } + + /** + * Sets the value of the flag that controls whether or not connections being returned to the pool will be checked + * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit + * setting is {@code false} when the connection is returned. It is {@code true} by default. + * + * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured + * with auto-commit. + * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}. + */ + @Deprecated + public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { + this.autoCommitOnReturn = autoCommitOnReturn; + } + + /** + * Sets the EvictionPolicy implementation to use with this connection pool. + * + * @param evictionPolicyClassName The fully qualified class name of the EvictionPolicy implementation + */ + public synchronized void setEvictionPolicyClassName(final String evictionPolicyClassName) { + setConnectionPool(GenericObjectPool::setEvictionPolicyClassName, evictionPolicyClassName); + this.evictionPolicyClassName = evictionPolicyClassName; + } + + /** + * @see #getFastFailValidation() + * @param fastFailValidation true means connections created by this factory will fast fail validation + * @since 2.1 + */ + public void setFastFailValidation(final boolean fastFailValidation) { + this.fastFailValidation = fastFailValidation; + } + + /** + * Sets the initial size of the connection pool. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param initialSize the number of connections created when the pool is initialized + */ + public synchronized void setInitialSize(final int initialSize) { + this.initialSize = initialSize; + } + + /** + * Sets the JMX name that has been requested for this DataSource. If the requested name is not valid, an alternative + * may be chosen. This DataSource will attempt to register itself using this name. If another component registers + * this DataSource with JMX and this name is valid this name will be used in preference to any specified by the + * other component. + * + * @param jmxName The JMX name that has been requested for this DataSource + */ + public void setJmxName(final String jmxName) { + this.jmxName = jmxName; + } + + /** + * Sets the LIFO property. True means the pool behaves as a LIFO queue; false means FIFO. + * + * @param lifo the new value for the LIFO property + */ + public synchronized void setLifo(final boolean lifo) { + this.lifo = lifo; + setConnectionPool(GenericObjectPool::setLifo, lifo); + } + + /** + * @param logAbandoned new logAbandoned property value + */ + public void setLogAbandoned(final boolean logAbandoned) { + setAbandoned(AbandonedConfig::setLogAbandoned, logAbandoned); + } + + /** + * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or + * not log messages are generated when the pool closes connections due to maximum lifetime exceeded. Set this + * property to false to suppress log messages when connections expire. + * + * @param logExpiredConnections Whether or not log messages are generated when the pool closes connections due to + * maximum lifetime exceeded. + */ + public void setLogExpiredConnections(final boolean logExpiredConnections) { + this.logExpiredConnections = logExpiredConnections; + } + + /** + * BasicDataSource does NOT support this method. + * + *

+ * Sets the login timeout (in seconds) for connecting to the database. + *

+ *

+ * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. + *

+ * + * @param loginTimeout The new login timeout, or zero for no timeout + * @throws UnsupportedOperationException If the DataSource implementation does not support the login timeout + * feature. + * @throws SQLException if a database access error occurs + */ + @Override + public void setLoginTimeout(final int loginTimeout) throws SQLException { + // This method isn't supported by the PoolingDataSource returned by the + // createDataSource + throw new UnsupportedOperationException("Not supported by BasicDataSource"); + } + + /** + * Sets the log writer being used by this data source. + *

+ * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool. + *

+ * + * @param logWriter The new log writer + * @throws SQLException if a database access error occurs + */ + @Override + public void setLogWriter(final PrintWriter logWriter) throws SQLException { + createDataSource().setLogWriter(logWriter); + this.logWriter = logWriter; + } + + /** + * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an + * infinite lifetime. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param maxConnDuration The maximum permitted lifetime of a connection. + * @since 2.10.0 + */ + public void setMaxConn(final Duration maxConnDuration) { + this.maxConnDuration = maxConnDuration; + } + + /** + * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an + * infinite lifetime. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds. + * @deprecated Use {@link #setMaxConn(Duration)}. + */ + @Deprecated + public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { + this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis); + } + + /** + * Sets the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed on + * return to the pool. + * + * @see #getMaxIdle() + * @param maxIdle the new value for maxIdle + */ + public synchronized void setMaxIdle(final int maxIdle) { + this.maxIdle = maxIdle; + setConnectionPool(GenericObjectPool::setMaxIdle, maxIdle); + } + + /** + * Sets the value of the {@code maxOpenPreparedStatements} property. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param maxOpenStatements the new maximum number of prepared statements + */ + public synchronized void setMaxOpenPreparedStatements(final int maxOpenStatements) { + this.maxOpenPreparedStatements = maxOpenStatements; + } + + /** + * Sets the maximum total number of idle and borrows connections that can be active at the same time. Use a negative + * value for no limit. + * + * @param maxTotal the new value for maxTotal + * @see #getMaxTotal() + */ + public synchronized void setMaxTotal(final int maxTotal) { + this.maxTotal = maxTotal; + setConnectionPool(GenericObjectPool::setMaxTotal, maxTotal); + } + + /** + * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely. + * + * @param maxWaitDuration the new value for MaxWaitMillis + * @see #getMaxWaitDuration() + * @since 2.10.0 + */ + public synchronized void setMaxWait(final Duration maxWaitDuration) { + this.maxWaitDuration = maxWaitDuration; + setConnectionPool(GenericObjectPool::setMaxWait, maxWaitDuration); + } + + /** + * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely. + * + * @param maxWaitMillis the new value for MaxWaitMillis + * @see #getMaxWaitDuration() + * @deprecated {@link #setMaxWait(Duration)}. + */ + @Deprecated + public synchronized void setMaxWaitMillis(final long maxWaitMillis) { + setMaxWait(Duration.ofMillis(maxWaitMillis)); + } + + /** + * Sets the {code minEvictableIdleDuration} property. + * + * @param minEvictableIdleDuration the minimum amount of time an object may sit idle in the pool + * @see #setMinEvictableIdle(Duration) + * @since 2.10.0 + */ + public synchronized void setMinEvictableIdle(final Duration minEvictableIdleDuration) { + this.minEvictableIdleDuration = minEvictableIdleDuration; + setConnectionPool(GenericObjectPool::setMinEvictableIdleDuration, minEvictableIdleDuration); + } + + /** + * Sets the {code minEvictableIdleDuration} property. + * + * @param minEvictableIdleTimeMillis the minimum amount of time an object may sit idle in the pool + * @see #setMinEvictableIdle(Duration) + * @deprecated Use {@link #setMinEvictableIdle(Duration)}. + */ + @Deprecated + public synchronized void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) { + setMinEvictableIdle(Duration.ofMillis(minEvictableIdleTimeMillis)); + } + + /** + * Sets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections are + * available when the idle object evictor runs. The value of this property has no effect unless + * {code durationBetweenEvictionRuns} has a positive value. + * + * @param minIdle the new value for minIdle + * @see GenericObjectPool#setMinIdle(int) + */ + public synchronized void setMinIdle(final int minIdle) { + this.minIdle = minIdle; + setConnectionPool(GenericObjectPool::setMinIdle, minIdle); + } + + /** + * Sets the value of the {code numTestsPerEvictionRun} property. + * + * @param numTestsPerEvictionRun the new {code numTestsPerEvictionRun} value + * @see #setNumTestsPerEvictionRun(int) + */ + public synchronized void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { + this.numTestsPerEvictionRun = numTestsPerEvictionRun; + setConnectionPool(GenericObjectPool::setNumTestsPerEvictionRun, numTestsPerEvictionRun); + } + + /** + * Sets the {code password}. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param password new value for the password + */ + public void setPassword(final String password) { + this.password = password; + } + + /** + * Sets whether to pool statements or not. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param poolingStatements pooling on or off + */ + public synchronized void setPoolPreparedStatements(final boolean poolingStatements) { + this.poolPreparedStatements = poolingStatements; + } + + /** + * Sets if connection level JMX tracking is requested for this DataSource. If true, each connection will be + * registered for tracking with JMX. + * + * @param registerConnectionMBean connection tracking requested for this DataSource. + */ + public void setRegisterConnectionMBean(final boolean registerConnectionMBean) { + this.registerConnectionMBean = registerConnectionMBean; + } + + /** + * @param removeAbandonedOnBorrow true means abandoned connections may be removed when connections are borrowed from + * the pool. + * @see #getRemoveAbandonedOnBorrow() + */ + public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) { + setAbandoned(AbandonedConfig::setRemoveAbandonedOnBorrow, removeAbandonedOnBorrow); + } + + /** + * @param removeAbandonedOnMaintenance true means abandoned connections may be removed on pool maintenance. + * @see #getRemoveAbandonedOnMaintenance() + */ + public void setRemoveAbandonedOnMaintenance(final boolean removeAbandonedOnMaintenance) { + setAbandoned(AbandonedConfig::setRemoveAbandonedOnMaintenance, removeAbandonedOnMaintenance); + } + + /** + * Sets the timeout before an abandoned connection can be removed. + *

+ * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and + * {code getRemoveAbandonedOnMaintenance()} are false. + *

+ * + * @param removeAbandonedTimeout new abandoned timeout + * @see #getRemoveAbandonedTimeoutDuration() + * @see #getRemoveAbandonedOnBorrow() + * @see #getRemoveAbandonedOnMaintenance() + * @since 2.10.0 + */ + public void setRemoveAbandonedTimeout(final Duration removeAbandonedTimeout) { + setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, removeAbandonedTimeout); + } + + /** + * Sets the timeout in seconds before an abandoned connection can be removed. + *

+ * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and + * {@link #getRemoveAbandonedOnMaintenance()} are false. + *

+ * + * @param removeAbandonedTimeout new abandoned timeout in seconds + * @see #getRemoveAbandonedTimeoutDuration() + * @see #getRemoveAbandonedOnBorrow() + * @see #getRemoveAbandonedOnMaintenance() + * @deprecated Use {@link #setRemoveAbandonedTimeout(Duration)}. + */ + @Deprecated + public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) { + setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, Duration.ofSeconds(removeAbandonedTimeout)); + } + + /** + * Sets the flag that controls if a connection will be rolled back when it is returned to the pool if auto commit is + * not enabled and the connection is not read only. + * + * @param rollbackOnReturn whether a connection will be rolled back when it is returned to the pool. + */ + public void setRollbackOnReturn(final boolean rollbackOnReturn) { + this.rollbackOnReturn = rollbackOnReturn; + } + + /** + * Sets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the + * idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. + * + * @param softMinEvictableIdleTimeMillis minimum amount of time a connection may sit idle in the pool before it is + * eligible for eviction, assuming there are minIdle idle connections in the + * pool. + * @see #getSoftMinEvictableIdleTimeMillis + * @since 2.10.0 + */ + public synchronized void setSoftMinEvictableIdle(final Duration softMinEvictableIdleTimeMillis) { + this.softMinEvictableIdleDuration = softMinEvictableIdleTimeMillis; + setConnectionPool(GenericObjectPool::setSoftMinEvictableIdleDuration, softMinEvictableIdleTimeMillis); + } + + /** + * Sets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the + * idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool. + * + * @param softMinEvictableIdleTimeMillis minimum amount of time a connection may sit idle in the pool before it is + * eligible for eviction, assuming there are minIdle idle connections in the + * pool. + * @see #getSoftMinEvictableIdleTimeMillis + * @deprecated Use {@link #setSoftMinEvictableIdle(Duration)}. + */ + @Deprecated + public synchronized void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) { + setSoftMinEvictableIdle(Duration.ofMillis(softMinEvictableIdleTimeMillis)); + } + + /** + * Sets the {code testOnBorrow} property. This property determines whether or not the pool will validate objects + * before they are borrowed from the pool. + * + * @param testOnBorrow new value for testOnBorrow property + */ + public synchronized void setTestOnBorrow(final boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + setConnectionPool(GenericObjectPool::setTestOnBorrow, testOnBorrow); + } + + /** + * Sets the {code testOnCreate} property. This property determines whether or not the pool will validate objects + * immediately after they are created by the pool + * + * @param testOnCreate new value for testOnCreate property + */ + public synchronized void setTestOnCreate(final boolean testOnCreate) { + this.testOnCreate = testOnCreate; + setConnectionPool(GenericObjectPool::setTestOnCreate, testOnCreate); + } + + /** + * Sets the {@code testOnReturn} property. This property determines whether or not the pool will validate + * objects before they are returned to the pool. + * + * @param testOnReturn new value for testOnReturn property + */ + public synchronized void setTestOnReturn(final boolean testOnReturn) { + this.testOnReturn = testOnReturn; + setConnectionPool(GenericObjectPool::setTestOnReturn, testOnReturn); + } + + /** + * Sets the {@code testWhileIdle} property. This property determines whether or not the idle object evictor + * will validate connections. + * + * @param testWhileIdle new value for testWhileIdle property + */ + public synchronized void setTestWhileIdle(final boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + setConnectionPool(GenericObjectPool::setTestWhileIdle, testWhileIdle); + } + + /** + * Sets the {code durationBetweenEvictionRuns} property. + * + * @param timeBetweenEvictionRunsMillis the new time between evictor runs + * @see #setDurationBetweenEvictionRuns(Duration) + * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}. + */ + @Deprecated + public synchronized void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { + setDurationBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis)); + } + + /** + * Sets the {code connection string}. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param connectionString the new value for the JDBC connection connectionString + */ + public synchronized void setUrl(final String connectionString) { + this.connectionString = connectionString; + } + + /** + * Sets the {code userName}. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param userName the new value for the JDBC connection user name + */ + public void setUsername(final String userName) { + this.userName = userName; + } + + /** + * Sets the {code validationQuery}. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param validationQuery the new value for the validation query + */ + public void setValidationQuery(final String validationQuery) { + this.validationQuery = isEmpty(validationQuery) ? null : validationQuery; + } + + /** + * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a + * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param validationQueryTimeoutDuration new validation query timeout value in seconds + * @since 2.10.0 + */ + public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { + this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; + } + + /** + * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a + * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. + *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, + * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}. + *

+ * + * @param validationQueryTimeoutSeconds new validation query timeout value in seconds + * @deprecated Use {@link #setValidationQueryTimeout(Duration)}. + */ + @Deprecated + public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { + this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); + } + + /** + * Starts the datasource. + *

+ * It is not necessary to call this method before using a newly created BasicDataSource instance, but + * calling it in that context causes the datasource to be immediately initialized (instead of waiting for + * the first {@link #getConnection()} request). Its primary use is to restart and reinitialize a + * datasource that has been closed. + *

+ * When this method is called after {@link #close()}, connections checked out by clients + * before the datasource was stopped do not count in {@link #getMaxTotal()} or {@link #getNumActive()}. + * For example, if there are 3 connections checked out by clients when {@link #close()} is invoked and they are + * not returned before {@link #start()} is invoked, after this method is called, {@link #getNumActive()} will + * return 0. These connections will be physically closed when they are returned, but they will not count against + * the maximum allowed in the newly started datasource. + * + * @throws SQLException if an error occurs initializing the datasource + */ + @Override + public synchronized void start() throws SQLException { + closed = false; + createDataSource(); + } + + /** + * Starts the connection pool maintenance task, if configured. + */ + protected void startPoolMaintenance() { + if (connectionPool != null && durationBetweenEvictionRuns.compareTo(Duration.ZERO) > 0) { + connectionPool.setDurationBetweenEvictionRuns(durationBetweenEvictionRuns); + } + } + + private Collector> toLinkedHashSet() { + return Collectors.toCollection(LinkedHashSet::new); + } + + @Override + public T unwrap(final Class iface) throws SQLException { + if (isWrapperFor(iface)) { + return iface.cast(this); + } + throw new SQLException(this + " is not a wrapper for " + iface); + } + + private void updateJmxName(final GenericObjectPoolConfig config) { + if (registeredJmxObjectName == null) { + return; + } + final StringBuilder base = new StringBuilder(registeredJmxObjectName.toString()); + base.append(Constants.JMX_CONNECTION_POOL_BASE_EXT); + config.setJmxNameBase(base.toString()); + config.setJmxNamePrefix(Constants.JMX_CONNECTION_POOL_PREFIX); + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java index 5635816ec5..79b8a2137d 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java @@ -1,469 +1,469 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.function.Consumer; -import java.util.function.Function; - -import javax.naming.Context; -import javax.naming.Name; -import javax.naming.RefAddr; -import javax.naming.Reference; -import javax.naming.spi.ObjectFactory; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.commons.pool2.impl.BaseObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - -/** - * JNDI object factory that creates an instance of {@code BasicDataSource} that has been configured based on the - * {@code RefAddr} values of the specified {@code Reference}, which must match the names and data types of the - * {@code BasicDataSource} bean properties with the following exceptions: - *

    - *
  • {@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the - * statements whereas {@code BasicDataSource} requires a collection of Strings.
  • - *
- * - * @since 2.0 - */ -public class BasicDataSourceFactory implements ObjectFactory { - - private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class); - - private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit"; - private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly"; - private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation"; - private static final String PROP_DEFAULT_CATALOG = "defaultCatalog"; - private static final String PROP_DEFAULT_SCHEMA = "defaultSchema"; - private static final String PROP_CACHE_STATE = "cacheState"; - private static final String PROP_DRIVER_CLASS_NAME = "driverClassName"; - private static final String PROP_LIFO = "lifo"; - private static final String PROP_MAX_TOTAL = "maxTotal"; - private static final String PROP_MAX_IDLE = "maxIdle"; - private static final String PROP_MIN_IDLE = "minIdle"; - private static final String PROP_INITIAL_SIZE = "initialSize"; - private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis"; - private static final String PROP_TEST_ON_CREATE = "testOnCreate"; - private static final String PROP_TEST_ON_BORROW = "testOnBorrow"; - private static final String PROP_TEST_ON_RETURN = "testOnReturn"; - private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis"; - private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; - private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; - private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis"; - private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName"; - private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle"; - private static final String PROP_PASSWORD = Constants.KEY_PASSWORD; - private static final String PROP_URL = "url"; - private static final String PROP_USER_NAME = "username"; - private static final String PROP_VALIDATION_QUERY = "validationQuery"; - private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout"; - private static final String PROP_JMX_NAME = "jmxName"; - private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean"; - private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName"; - - /** - * The property name for connectionInitSqls. The associated value String must be of the form [query;]* - */ - private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls"; - private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed"; - private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow"; - private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance"; - private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout"; - private static final String PROP_LOG_ABANDONED = "logAbandoned"; - private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking"; - private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements"; - private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn"; - private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements"; - private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties"; - private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis"; - private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections"; - private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn"; - private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn"; - private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout"; - private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation"; - - /** - * Value string must be of the form [STATE_CODE,]* - */ - private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes"; - - /** - * Property key for specifying the SQL State codes that should be ignored during disconnection checks. - *

- * The value for this property must be a comma-separated string of SQL State codes, where each code represents - * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series - * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004"). - *

- * @since 2.13.0 - */ - private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes"; - - /* - * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x - * properties. - */ - private static final String NUPROP_MAX_ACTIVE = "maxActive"; - private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned"; - private static final String NUPROP_MAXWAIT = "maxWait"; - - /* - * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may - * appear in Resource, and not listing them as ignored. - */ - private static final String SILENT_PROP_FACTORY = "factory"; - private static final String SILENT_PROP_SCOPE = "scope"; - private static final String SILENT_PROP_SINGLETON = "singleton"; - private static final String SILENT_PROP_AUTH = "auth"; - - private static final List ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY, - PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE, - PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE, - PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN, - PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, - PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD, - PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS, - PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, - PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS, - PROP_CLEAR_STATEMENT_POOL_ON_RETURN, - PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS, - PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, - PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES, - PROP_JMX_NAME, PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME); - - /** - * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee - * that properties will be listed to output in order of insertion into map. - */ - private static final Map NUPROP_WARNTEXT = new LinkedHashMap<>(); - - static { - NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE, - "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. " - + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + "."); - NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED, - "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of " - + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. " - + "Both have default value set to false."); - NUPROP_WARNTEXT.put(NUPROP_MAXWAIT, - "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. " - + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT - + "."); - } - - /** - * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource - * references, and we will not list them as ignored. - */ - private static final List SILENT_PROPERTIES = new ArrayList<>(); - - static { - SILENT_PROPERTIES.add(SILENT_PROP_FACTORY); - SILENT_PROPERTIES.add(SILENT_PROP_SCOPE); - SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON); - SILENT_PROPERTIES.add(SILENT_PROP_AUTH); - - } - - private static void accept(final Properties properties, final String name, final Function parser, final Consumer consumer) { - getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v))); - } - - private static void acceptBoolean(final Properties properties, final String name, final Consumer consumer) { - accept(properties, name, Boolean::parseBoolean, consumer); - } - - private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer consumer) { - accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer); - } - - private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer consumer) { - accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer); - } - - private static void acceptInt(final Properties properties, final String name, final Consumer consumer) { - accept(properties, name, Integer::parseInt, consumer); - } - - private static void acceptString(final Properties properties, final String name, final Consumer consumer) { - accept(properties, name, Function.identity(), consumer); - } - - /** - * Creates and configures a {@link BasicDataSource} instance based on the given properties. - * - * @param properties - * The data source configuration properties. - * @return A new a {@link BasicDataSource} instance based on the given properties. - * @throws SQLException - * Thrown when an error occurs creating the data source. - */ - public static BasicDataSource createDataSource(final Properties properties) throws SQLException { - final BasicDataSource dataSource = new BasicDataSource(); - acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit); - acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly); - - getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> { - value = value.toUpperCase(Locale.ROOT); - int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; - switch (value) { - case "NONE": - level = Connection.TRANSACTION_NONE; - break; - case "READ_COMMITTED": - level = Connection.TRANSACTION_READ_COMMITTED; - break; - case "READ_UNCOMMITTED": - level = Connection.TRANSACTION_READ_UNCOMMITTED; - break; - case "REPEATABLE_READ": - level = Connection.TRANSACTION_REPEATABLE_READ; - break; - case "SERIALIZABLE": - level = Connection.TRANSACTION_SERIALIZABLE; - break; - default: - try { - level = Integer.parseInt(value); - } catch (final NumberFormatException e) { - System.err.println("Could not parse defaultTransactionIsolation: " + value); - System.err.println("WARNING: defaultTransactionIsolation not set"); - System.err.println("using default value of database driver"); - level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; - } - break; - } - dataSource.setDefaultTransactionIsolation(level); - }); - - acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema); - acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog); - acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState); - acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName); - acceptBoolean(properties, PROP_LIFO, dataSource::setLifo); - acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal); - acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle); - acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle); - acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize); - acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait); - acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate); - acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow); - acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn); - acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns); - acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun); - acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle); - acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle); - acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName); - acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle); - acceptString(properties, PROP_PASSWORD, dataSource::setPassword); - acceptString(properties, PROP_URL, dataSource::setUrl); - acceptString(properties, PROP_USER_NAME, dataSource::setUsername); - acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery); - acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout); - acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed); - acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow); - acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance); - acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout); - acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned); - acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking); - acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements); - acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn); - acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements); - getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';'))); - - final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES); - if (value != null) { - for (final Object key : getProperties(value).keySet()) { - final String propertyName = Objects.toString(key, null); - dataSource.addConnectionProperty(propertyName, getProperties(value).getProperty(propertyName)); - } - } - - acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn); - acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections); - acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName); - acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean); - acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn); - acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn); - acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout); - acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation); - getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ','))); - getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ','))); - acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName); - - // DBCP-215 - // Trick to make sure that initialSize connections are created - if (dataSource.getInitialSize() > 0) { - dataSource.getLogWriter(); - } - - // Return the configured DataSource instance - return dataSource; - } - - private static Optional getOptional(final Properties properties, final String name) { - return Optional.ofNullable(properties.getProperty(name)); - } - - /** - * Parse properties from the string. Format of the string must be [propertyName=property;]* - * - * @param propText The source text - * @return Properties A new Properties instance - * @throws SQLException When a paring exception occurs - */ - private static Properties getProperties(final String propText) throws SQLException { - final Properties p = new Properties(); - if (propText != null) { - try { - p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1))); - } catch (final IOException e) { - throw new SQLException(propText, e); - } - } - return p; - } - - /** - * Parses list of property values from a delimited string - * - * @param value - * delimited list of values - * @param delimiter - * character used to separate values in the list - * @return String Collection of values - */ - private static List parseList(final String value, final char delimiter) { - final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter)); - final List tokens = new ArrayList<>(tokenizer.countTokens()); - while (tokenizer.hasMoreTokens()) { - tokens.add(tokenizer.nextToken()); - } - return tokens; - } - - /** - * Creates and return a new {@code BasicDataSource} instance. If no instance can be created, return - * {@code null} instead. - * - * @param obj - * The possibly null object containing location or reference information that can be used in creating an - * object - * @param name - * The name of this object relative to {@code nameCtx} - * @param nameCtx - * The context relative to which the {@code name} parameter is specified, or {@code null} if - * {@code name} is relative to the default initial context - * @param environment - * The possibly null environment that is used in creating this object - * - * @throws SQLException - * if an exception occurs creating the instance - */ - @Override - public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, - final Hashtable environment) throws SQLException { - - // We only know how to deal with {@code javax.naming.Reference}s - // that specify a class name of "javax.sql.DataSource" - if (obj == null || !(obj instanceof Reference)) { - return null; - } - final Reference ref = (Reference) obj; - if (!"javax.sql.DataSource".equals(ref.getClassName())) { - return null; - } - - // Check property names and log warnings about obsolete and / or unknown properties - final List warnMessages = new ArrayList<>(); - final List infoMessages = new ArrayList<>(); - validatePropertyNames(ref, name, warnMessages, infoMessages); - warnMessages.forEach(log::warn); - infoMessages.forEach(log::info); - - final Properties properties = new Properties(); - ALL_PROPERTY_NAMES.forEach(propertyName -> { - final RefAddr ra = ref.get(propertyName); - if (ra != null) { - properties.setProperty(propertyName, Objects.toString(ra.getContent(), null)); - } - }); - - return createDataSource(properties); - } - - /** - * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties - * generate info messages. - * - * @param ref - * Reference to check properties of - * @param name - * Name provided to getObject - * @param warnMessages - * container for warning messages - * @param infoMessages - * container for info messages - */ - private void validatePropertyNames(final Reference ref, final Name name, final List warnMessages, - final List infoMessages) { - final String nameString = name != null ? "Name = " + name.toString() + " " : ""; - NUPROP_WARNTEXT.forEach((propertyName, value) -> { - final RefAddr ra = ref.get(propertyName); - if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) { - final StringBuilder stringBuilder = new StringBuilder(nameString); - final String propertyValue = Objects.toString(ra.getContent(), null); - stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName) - .append("\" property, which is being ignored."); - warnMessages.add(stringBuilder.toString()); - } - }); - - final Enumeration allRefAddrs = ref.getAll(); - while (allRefAddrs.hasMoreElements()) { - final RefAddr ra = allRefAddrs.nextElement(); - final String propertyName = ra.getType(); - // If property name is not in the properties list, we haven't warned on it - // and it is not in the "silent" list, tell user we are ignoring it. - if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) { - final String propertyValue = Objects.toString(ra.getContent(), null); - final StringBuilder stringBuilder = new StringBuilder(nameString); - stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName) - .append("\" property"); - infoMessages.add(stringBuilder.toString()); - } - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +/** + * JNDI object factory that creates an instance of {@code BasicDataSource} that has been configured based on the + * {@code RefAddr} values of the specified {@code Reference}, which must match the names and data types of the + * {@code BasicDataSource} bean properties with the following exceptions: + *
    + *
  • {@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the + * statements whereas {@code BasicDataSource} requires a collection of Strings.
  • + *
+ * + * @since 2.0 + */ +public class BasicDataSourceFactory implements ObjectFactory { + + private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class); + + private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit"; + private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly"; + private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation"; + private static final String PROP_DEFAULT_CATALOG = "defaultCatalog"; + private static final String PROP_DEFAULT_SCHEMA = "defaultSchema"; + private static final String PROP_CACHE_STATE = "cacheState"; + private static final String PROP_DRIVER_CLASS_NAME = "driverClassName"; + private static final String PROP_LIFO = "lifo"; + private static final String PROP_MAX_TOTAL = "maxTotal"; + private static final String PROP_MAX_IDLE = "maxIdle"; + private static final String PROP_MIN_IDLE = "minIdle"; + private static final String PROP_INITIAL_SIZE = "initialSize"; + private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis"; + private static final String PROP_TEST_ON_CREATE = "testOnCreate"; + private static final String PROP_TEST_ON_BORROW = "testOnBorrow"; + private static final String PROP_TEST_ON_RETURN = "testOnReturn"; + private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis"; + private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; + private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; + private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis"; + private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName"; + private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle"; + private static final String PROP_PASSWORD = Constants.KEY_PASSWORD; + private static final String PROP_URL = "url"; + private static final String PROP_USER_NAME = "username"; + private static final String PROP_VALIDATION_QUERY = "validationQuery"; + private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout"; + private static final String PROP_JMX_NAME = "jmxName"; + private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean"; + private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName"; + + /** + * The property name for connectionInitSqls. The associated value String must be of the form [query;]* + */ + private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls"; + private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed"; + private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow"; + private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance"; + private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout"; + private static final String PROP_LOG_ABANDONED = "logAbandoned"; + private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking"; + private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements"; + private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn"; + private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements"; + private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties"; + private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis"; + private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections"; + private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn"; + private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn"; + private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout"; + private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation"; + + /** + * Value string must be of the form [STATE_CODE,]* + */ + private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes"; + + /** + * Property key for specifying the SQL State codes that should be ignored during disconnection checks. + *

+ * The value for this property must be a comma-separated string of SQL State codes, where each code represents + * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series + * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004"). + *

+ * @since 2.13.0 + */ + private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes"; + + /* + * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x + * properties. + */ + private static final String NUPROP_MAX_ACTIVE = "maxActive"; + private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned"; + private static final String NUPROP_MAXWAIT = "maxWait"; + + /* + * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may + * appear in Resource, and not listing them as ignored. + */ + private static final String SILENT_PROP_FACTORY = "factory"; + private static final String SILENT_PROP_SCOPE = "scope"; + private static final String SILENT_PROP_SINGLETON = "singleton"; + private static final String SILENT_PROP_AUTH = "auth"; + + private static final List ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY, + PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE, + PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE, + PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN, + PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, + PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD, + PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS, + PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, + PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS, + PROP_CLEAR_STATEMENT_POOL_ON_RETURN, + PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS, + PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, + PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES, + PROP_JMX_NAME, PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME); + + /** + * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee + * that properties will be listed to output in order of insertion into map. + */ + private static final Map NUPROP_WARNTEXT = new LinkedHashMap<>(); + + static { + NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE, + "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. " + + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + "."); + NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED, + "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of " + + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. " + + "Both have default value set to false."); + NUPROP_WARNTEXT.put(NUPROP_MAXWAIT, + "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. " + + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT + + "."); + } + + /** + * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource + * references, and we will not list them as ignored. + */ + private static final List SILENT_PROPERTIES = new ArrayList<>(); + + static { + SILENT_PROPERTIES.add(SILENT_PROP_FACTORY); + SILENT_PROPERTIES.add(SILENT_PROP_SCOPE); + SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON); + SILENT_PROPERTIES.add(SILENT_PROP_AUTH); + + } + + private static void accept(final Properties properties, final String name, final Function parser, final Consumer consumer) { + getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v))); + } + + private static void acceptBoolean(final Properties properties, final String name, final Consumer consumer) { + accept(properties, name, Boolean::parseBoolean, consumer); + } + + private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer consumer) { + accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer); + } + + private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer consumer) { + accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer); + } + + private static void acceptInt(final Properties properties, final String name, final Consumer consumer) { + accept(properties, name, Integer::parseInt, consumer); + } + + private static void acceptString(final Properties properties, final String name, final Consumer consumer) { + accept(properties, name, Function.identity(), consumer); + } + + /** + * Creates and configures a {@link BasicDataSource} instance based on the given properties. + * + * @param properties + * The data source configuration properties. + * @return A new a {@link BasicDataSource} instance based on the given properties. + * @throws SQLException + * Thrown when an error occurs creating the data source. + */ + public static BasicDataSource createDataSource(final Properties properties) throws SQLException { + final BasicDataSource dataSource = new BasicDataSource(); + acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit); + acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly); + + getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> { + value = value.toUpperCase(Locale.ROOT); + int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; + switch (value) { + case "NONE": + level = Connection.TRANSACTION_NONE; + break; + case "READ_COMMITTED": + level = Connection.TRANSACTION_READ_COMMITTED; + break; + case "READ_UNCOMMITTED": + level = Connection.TRANSACTION_READ_UNCOMMITTED; + break; + case "REPEATABLE_READ": + level = Connection.TRANSACTION_REPEATABLE_READ; + break; + case "SERIALIZABLE": + level = Connection.TRANSACTION_SERIALIZABLE; + break; + default: + try { + level = Integer.parseInt(value); + } catch (final NumberFormatException e) { + System.err.println("Could not parse defaultTransactionIsolation: " + value); + System.err.println("WARNING: defaultTransactionIsolation not set"); + System.err.println("using default value of database driver"); + level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION; + } + break; + } + dataSource.setDefaultTransactionIsolation(level); + }); + + acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema); + acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog); + acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState); + acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName); + acceptBoolean(properties, PROP_LIFO, dataSource::setLifo); + acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal); + acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle); + acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle); + acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize); + acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait); + acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate); + acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow); + acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn); + acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns); + acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun); + acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle); + acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle); + acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName); + acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle); + acceptString(properties, PROP_PASSWORD, dataSource::setPassword); + acceptString(properties, PROP_URL, dataSource::setUrl); + acceptString(properties, PROP_USER_NAME, dataSource::setUsername); + acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery); + acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout); + acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed); + acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow); + acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance); + acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout); + acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned); + acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking); + acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements); + acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn); + acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements); + getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';'))); + + final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES); + if (value != null) { + for (final Object key : getProperties(value).keySet()) { + final String propertyName = Objects.toString(key, null); + dataSource.addConnectionProperty(propertyName, getProperties(value).getProperty(propertyName)); + } + } + + acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn); + acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections); + acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName); + acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean); + acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn); + acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn); + acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout); + acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation); + getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ','))); + getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ','))); + acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName); + + // DBCP-215 + // Trick to make sure that initialSize connections are created + if (dataSource.getInitialSize() > 0) { + dataSource.getLogWriter(); + } + + // Return the configured DataSource instance + return dataSource; + } + + private static Optional getOptional(final Properties properties, final String name) { + return Optional.ofNullable(properties.getProperty(name)); + } + + /** + * Parse properties from the string. Format of the string must be [propertyName=property;]* + * + * @param propText The source text + * @return Properties A new Properties instance + * @throws SQLException When a paring exception occurs + */ + private static Properties getProperties(final String propText) throws SQLException { + final Properties p = new Properties(); + if (propText != null) { + try { + p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1))); + } catch (final IOException e) { + throw new SQLException(propText, e); + } + } + return p; + } + + /** + * Parses list of property values from a delimited string + * + * @param value + * delimited list of values + * @param delimiter + * character used to separate values in the list + * @return String Collection of values + */ + private static List parseList(final String value, final char delimiter) { + final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter)); + final List tokens = new ArrayList<>(tokenizer.countTokens()); + while (tokenizer.hasMoreTokens()) { + tokens.add(tokenizer.nextToken()); + } + return tokens; + } + + /** + * Creates and return a new {@code BasicDataSource} instance. If no instance can be created, return + * {@code null} instead. + * + * @param obj + * The possibly null object containing location or reference information that can be used in creating an + * object + * @param name + * The name of this object relative to {@code nameCtx} + * @param nameCtx + * The context relative to which the {@code name} parameter is specified, or {@code null} if + * {@code name} is relative to the default initial context + * @param environment + * The possibly null environment that is used in creating this object + * + * @throws SQLException + * if an exception occurs creating the instance + */ + @Override + public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, + final Hashtable environment) throws SQLException { + + // We only know how to deal with {@code javax.naming.Reference}s + // that specify a class name of "javax.sql.DataSource" + if (obj == null || !(obj instanceof Reference)) { + return null; + } + final Reference ref = (Reference) obj; + if (!"javax.sql.DataSource".equals(ref.getClassName())) { + return null; + } + + // Check property names and log warnings about obsolete and / or unknown properties + final List warnMessages = new ArrayList<>(); + final List infoMessages = new ArrayList<>(); + validatePropertyNames(ref, name, warnMessages, infoMessages); + warnMessages.forEach(log::warn); + infoMessages.forEach(log::info); + + final Properties properties = new Properties(); + ALL_PROPERTY_NAMES.forEach(propertyName -> { + final RefAddr ra = ref.get(propertyName); + if (ra != null) { + properties.setProperty(propertyName, Objects.toString(ra.getContent(), null)); + } + }); + + return createDataSource(properties); + } + + /** + * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties + * generate info messages. + * + * @param ref + * Reference to check properties of + * @param name + * Name provided to getObject + * @param warnMessages + * container for warning messages + * @param infoMessages + * container for info messages + */ + private void validatePropertyNames(final Reference ref, final Name name, final List warnMessages, + final List infoMessages) { + final String nameString = name != null ? "Name = " + name.toString() + " " : ""; + NUPROP_WARNTEXT.forEach((propertyName, value) -> { + final RefAddr ra = ref.get(propertyName); + if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) { + final StringBuilder stringBuilder = new StringBuilder(nameString); + final String propertyValue = Objects.toString(ra.getContent(), null); + stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName) + .append("\" property, which is being ignored."); + warnMessages.add(stringBuilder.toString()); + } + }); + + final Enumeration allRefAddrs = ref.getAll(); + while (allRefAddrs.hasMoreElements()) { + final RefAddr ra = allRefAddrs.nextElement(); + final String propertyName = ra.getType(); + // If property name is not in the properties list, we haven't warned on it + // and it is not in the "silent" list, tell user we are ignoring it. + if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) { + final String propertyValue = Objects.toString(ra.getContent(), null); + final StringBuilder stringBuilder = new StringBuilder(nameString); + stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName) + .append("\" property"); + infoMessages.add(stringBuilder.toString()); + } + } + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/ConnectionFactoryFactory.java b/src/main/java/org/apache/commons/dbcp2/ConnectionFactoryFactory.java index 23ce4e4b5e..d15d3d55da 100644 --- a/src/main/java/org/apache/commons/dbcp2/ConnectionFactoryFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/ConnectionFactoryFactory.java @@ -1,76 +1,76 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Driver; -import java.sql.SQLException; -import java.util.Properties; - -/* - * Creates {@link ConnectionFactory} instances. - * - * @since 2.7.0 - */ -final class ConnectionFactoryFactory { - - /** - * Creates a new {@link DriverConnectionFactory} allowing for an override through - * {@link BasicDataSource#getDriverClassName()}. - * - * @param basicDataSource Configures creation. - * @param driver The JDBC driver. - * @return a new {@link DriverConnectionFactory} allowing for a {@link BasicDataSource#getDriverClassName()} - * override. - * @throws SQLException Thrown when instantiation fails. - */ - static ConnectionFactory createConnectionFactory(final BasicDataSource basicDataSource, final Driver driver) - throws SQLException { - final Properties connectionProperties = basicDataSource.getConnectionProperties(); - final String url = basicDataSource.getUrl(); - // Set up the driver connection factory we will use - final String user = basicDataSource.getUserName(); - if (user != null) { - connectionProperties.put(Constants.KEY_USER, user); - } else { - basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_USER)); - } - - final String pwd = basicDataSource.getPassword(); - if (pwd != null) { - connectionProperties.put(Constants.KEY_PASSWORD, pwd); - } else { - basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_PASSWORD)); - } - final String connectionFactoryClassName = basicDataSource.getConnectionFactoryClassName(); - if (connectionFactoryClassName != null) { - try { - final Class connectionFactoryFromCCL = Class.forName(connectionFactoryClassName); - return (ConnectionFactory) connectionFactoryFromCCL - .getConstructor(Driver.class, String.class, Properties.class) - .newInstance(driver, url, connectionProperties); - } catch (final Exception t) { - final String message = "Cannot load ConnectionFactory implementation '" + connectionFactoryClassName - + "'"; - basicDataSource.log(message, t); - throw new SQLException(message, t); - } - } - // Defaults to DriverConnectionFactory - return new DriverConnectionFactory(driver, url, connectionProperties); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; + +/* + * Creates {@link ConnectionFactory} instances. + * + * @since 2.7.0 + */ +final class ConnectionFactoryFactory { + + /** + * Creates a new {@link DriverConnectionFactory} allowing for an override through + * {@link BasicDataSource#getDriverClassName()}. + * + * @param basicDataSource Configures creation. + * @param driver The JDBC driver. + * @return a new {@link DriverConnectionFactory} allowing for a {@link BasicDataSource#getDriverClassName()} + * override. + * @throws SQLException Thrown when instantiation fails. + */ + static ConnectionFactory createConnectionFactory(final BasicDataSource basicDataSource, final Driver driver) + throws SQLException { + final Properties connectionProperties = basicDataSource.getConnectionProperties(); + final String url = basicDataSource.getUrl(); + // Set up the driver connection factory we will use + final String user = basicDataSource.getUserName(); + if (user != null) { + connectionProperties.put(Constants.KEY_USER, user); + } else { + basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_USER)); + } + + final String pwd = basicDataSource.getPassword(); + if (pwd != null) { + connectionProperties.put(Constants.KEY_PASSWORD, pwd); + } else { + basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_PASSWORD)); + } + final String connectionFactoryClassName = basicDataSource.getConnectionFactoryClassName(); + if (connectionFactoryClassName != null) { + try { + final Class connectionFactoryFromCCL = Class.forName(connectionFactoryClassName); + return (ConnectionFactory) connectionFactoryFromCCL + .getConstructor(Driver.class, String.class, Properties.class) + .newInstance(driver, url, connectionProperties); + } catch (final Exception t) { + final String message = "Cannot load ConnectionFactory implementation '" + connectionFactoryClassName + + "'"; + basicDataSource.log(message, t); + throw new SQLException(message, t); + } + } + // Defaults to DriverConnectionFactory + return new DriverConnectionFactory(driver, url, connectionProperties); + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/DataSourceConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/DataSourceConnectionFactory.java index e7dbac3a4f..98d12d1808 100644 --- a/src/main/java/org/apache/commons/dbcp2/DataSourceConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/DataSourceConnectionFactory.java @@ -1,111 +1,111 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - -/** - * A {@link DataSource}-based implementation of {@link ConnectionFactory}. - * - * @since 2.0 - */ -public class DataSourceConnectionFactory implements ConnectionFactory { - - private final DataSource dataSource; - - private final String userName; - - private final char[] userPassword; - - /** - * Constructs an instance for the given DataSource. - * - * @param dataSource - * The DataSource for this factory. - */ - public DataSourceConnectionFactory(final DataSource dataSource) { - this(dataSource, null, (char[]) null); - } - - /** - * Constructs an instance for the given DataSource. - * - * @param dataSource - * The DataSource for this factory. - * @param userName - * The user name. - * @param userPassword - * The user password. - * @since 2.4.0 - */ - public DataSourceConnectionFactory(final DataSource dataSource, final String userName, final char[] userPassword) { - this.dataSource = dataSource; - this.userName = userName; - this.userPassword = Utils.clone(userPassword); - } - - /** - * Constructs an instance for the given DataSource. - * - * @param dataSource - * The DataSource for this factory. - * @param userName - * The user name. - * @param password - * The user password. - */ - public DataSourceConnectionFactory(final DataSource dataSource, final String userName, final String password) { - this.dataSource = dataSource; - this.userName = userName; - this.userPassword = Utils.toCharArray(password); - } - - @Override - public Connection createConnection() throws SQLException { - if (null == userName && null == userPassword) { - return dataSource.getConnection(); - } - return dataSource.getConnection(userName, Utils.toString(userPassword)); - } - - /** - * @return The data source. - * @since 2.6.0 - */ - public DataSource getDataSource() { - return dataSource; - } - - /** - * @return The user name. - * @since 2.6.0 - */ - public String getUserName() { - return userName; - } - - /** - * @return The user password. - * @since 2.6.0 - */ - public char[] getUserPassword() { - return Utils.clone(userPassword); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +/** + * A {@link DataSource}-based implementation of {@link ConnectionFactory}. + * + * @since 2.0 + */ +public class DataSourceConnectionFactory implements ConnectionFactory { + + private final DataSource dataSource; + + private final String userName; + + private final char[] userPassword; + + /** + * Constructs an instance for the given DataSource. + * + * @param dataSource + * The DataSource for this factory. + */ + public DataSourceConnectionFactory(final DataSource dataSource) { + this(dataSource, null, (char[]) null); + } + + /** + * Constructs an instance for the given DataSource. + * + * @param dataSource + * The DataSource for this factory. + * @param userName + * The user name. + * @param userPassword + * The user password. + * @since 2.4.0 + */ + public DataSourceConnectionFactory(final DataSource dataSource, final String userName, final char[] userPassword) { + this.dataSource = dataSource; + this.userName = userName; + this.userPassword = Utils.clone(userPassword); + } + + /** + * Constructs an instance for the given DataSource. + * + * @param dataSource + * The DataSource for this factory. + * @param userName + * The user name. + * @param password + * The user password. + */ + public DataSourceConnectionFactory(final DataSource dataSource, final String userName, final String password) { + this.dataSource = dataSource; + this.userName = userName; + this.userPassword = Utils.toCharArray(password); + } + + @Override + public Connection createConnection() throws SQLException { + if (null == userName && null == userPassword) { + return dataSource.getConnection(); + } + return dataSource.getConnection(userName, Utils.toString(userPassword)); + } + + /** + * @return The data source. + * @since 2.6.0 + */ + public DataSource getDataSource() { + return dataSource; + } + + /** + * @return The user name. + * @since 2.6.0 + */ + public String getUserName() { + return userName; + } + + /** + * @return The user password. + * @since 2.6.0 + */ + public char[] getUserPassword() { + return Utils.clone(userPassword); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java index 996d31f2b4..1ad95be1e4 100644 --- a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java @@ -1,1081 +1,1081 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Array; -import java.sql.Blob; -import java.sql.CallableStatement; -import java.sql.ClientInfoStatus; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.NClob; -import java.sql.PreparedStatement; -import java.sql.SQLClientInfoException; -import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Savepoint; -import java.sql.Statement; -import java.sql.Struct; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.Executor; - -import org.apache.commons.dbcp2.managed.ManagedConnection; - -/** - * A base delegating implementation of {@link Connection}. - *

- * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active, - * and call the corresponding method on the "delegate" provided in my constructor. - *

- *

- * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking - * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of - * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout. - *

- * - * @param - * the Connection type - * - * @since 2.0 - */ -public class DelegatingConnection extends AbandonedTrace implements Connection { - - private static final Map EMPTY_FAILED_PROPERTIES = Collections - .emptyMap(); - - /** My delegate {@link Connection}. */ - private volatile C connection; - - private volatile boolean closed; - - private boolean cacheState = true; - private Boolean cachedAutoCommit; - private Boolean cachedReadOnly; - private String cachedCatalog; - private String cachedSchema; - private Duration defaultQueryTimeoutDuration; - - /** - * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool. - * - * @param connection the {@link Connection} to delegate all calls to, may be null (see {@link ManagedConnection}). - */ - public DelegatingConnection(final C connection) { - this.connection = connection; - } - - @Override - public void abort(final Executor executor) throws SQLException { - try { - Jdbc41Bridge.abort(connection, executor); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Marks this instance as used and delegates to a wrapped {@link DelegatingConnection#activate()}. - */ - protected void activate() { - closed = false; - setLastUsed(); - if (connection instanceof DelegatingConnection) { - ((DelegatingConnection) connection).activate(); - } - } - - /** - * Throws a SQLException if this connection is not open. - * - * @throws SQLException Thrown if this connection is not open. - */ - protected void checkOpen() throws SQLException { - if (closed) { - if (null != connection) { - String label; - try { - label = connection.toString(); - } catch (final Exception e) { - // leave label empty - label = ""; - } - throw new SQLException("Connection " + label + " is closed."); - } - throw new SQLException("Connection is null."); - } - } - - /** - * Clears the cached state. Call when you know that the underlying connection may have been accessed - * directly. - */ - public void clearCachedState() { - cachedAutoCommit = null; - cachedReadOnly = null; - cachedSchema = null; - cachedCatalog = null; - if (connection instanceof DelegatingConnection) { - ((DelegatingConnection) connection).clearCachedState(); - } - } - - @Override - public void clearWarnings() throws SQLException { - checkOpen(); - try { - connection.clearWarnings(); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that - * override this method must: - *
    - *
  1. Call {@link #passivate()}
  2. - *
  3. Call close (or the equivalent appropriate action) on the wrapped connection
  4. - *
  5. Set {@code closed} to {@code false}
  6. - *
- */ - @Override - public void close() throws SQLException { - if (!closed) { - closeInternal(); - } - } - - protected final void closeInternal() throws SQLException { - try { - passivate(); - } finally { - if (connection != null) { - boolean connectionIsClosed; - try { - connectionIsClosed = connection.isClosed(); - } catch (final SQLException e) { - // not sure what the state is, so assume the connection is open. - connectionIsClosed = false; - } - try { - // DBCP-512: Avoid exceptions when closing a connection in multi-threaded use case. - // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when - // closing from multiple threads. - if (!connectionIsClosed) { - connection.close(); - } - } finally { - closed = true; - } - } else { - closed = true; - } - } - } - - @Override - public void commit() throws SQLException { - checkOpen(); - try { - connection.commit(); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException { - checkOpen(); - try { - return connection.createArrayOf(typeName, elements); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public Blob createBlob() throws SQLException { - checkOpen(); - try { - return connection.createBlob(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public Clob createClob() throws SQLException { - checkOpen(); - try { - return connection.createClob(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public NClob createNClob() throws SQLException { - checkOpen(); - try { - return connection.createNClob(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public SQLXML createSQLXML() throws SQLException { - checkOpen(); - try { - return connection.createSQLXML(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public Statement createStatement() throws SQLException { - checkOpen(); - try { - return init(new DelegatingStatement(this, connection.createStatement())); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException { - checkOpen(); - try { - return init(new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public Statement createStatement(final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - checkOpen(); - try { - return init(new DelegatingStatement(this, - connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException { - checkOpen(); - try { - return connection.createStruct(typeName, attributes); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public boolean getAutoCommit() throws SQLException { - checkOpen(); - if (cacheState && cachedAutoCommit != null) { - return cachedAutoCommit; - } - try { - cachedAutoCommit = connection.getAutoCommit(); - return cachedAutoCommit; - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - /** - * Gets whether to cache properties. The cached properties are: - *
    - *
  • auto-commit
  • - *
  • catalog
  • - *
  • schema
  • - *
  • read-only
  • - *
- * - * @return the state caching flag - */ - public boolean getCacheState() { - return cacheState; - } - - @Override - public String getCatalog() throws SQLException { - checkOpen(); - if (cacheState && cachedCatalog != null) { - return cachedCatalog; - } - try { - cachedCatalog = connection.getCatalog(); - return cachedCatalog; - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public Properties getClientInfo() throws SQLException { - checkOpen(); - try { - return connection.getClientInfo(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public String getClientInfo(final String name) throws SQLException { - checkOpen(); - try { - return connection.getClientInfo(name); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - /** - * Gets the default query timeout that will be used for {@link Statement}s created from this connection. - * {@code null} means that the driver default will be used. - * - * @return query timeout limit in seconds; zero means there is no limit. - * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. - */ - @Deprecated - public Integer getDefaultQueryTimeout() { - return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); - } - - /** - * Gets the default query timeout that will be used for {@link Statement}s created from this connection. - * {@code null} means that the driver default will be used. - * - * @return query timeout limit; zero means there is no limit. - * @since 2.10.0 - */ - public Duration getDefaultQueryTimeoutDuration() { - return defaultQueryTimeoutDuration; - } - - /** - * Returns my underlying {@link Connection}. - * - * @return my underlying {@link Connection}. - */ - public C getDelegate() { - return getDelegateInternal(); - } - - /** - * Gets the delegate connection. - * - * @return the delegate connection. - */ - protected final C getDelegateInternal() { - return connection; - } - - @Override - public int getHoldability() throws SQLException { - checkOpen(); - try { - return connection.getHoldability(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - /** - * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively - * invokes this method on my delegate. - *

- * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when - * no non-{@code DelegatingConnection} delegate can be found by traversing this chain. - *

- *

- * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain - * a "genuine" {@link Connection}. - *

- * - * @return innermost delegate. - */ - public Connection getInnermostDelegate() { - return getInnermostDelegateInternal(); - } - - /** - * Although this method is public, it is part of the internal API and should not be used by clients. The signature - * of this method may change at any time including in ways that break backwards compatibility. - * - * @return innermost delegate. - */ - @SuppressWarnings("resource") - public final Connection getInnermostDelegateInternal() { - Connection conn = connection; - while (conn instanceof DelegatingConnection) { - conn = ((DelegatingConnection) conn).getDelegateInternal(); - if (this == conn) { - return null; - } - } - return conn; - } - - @Override - public DatabaseMetaData getMetaData() throws SQLException { - checkOpen(); - try { - return new DelegatingDatabaseMetaData(this, connection.getMetaData()); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public int getNetworkTimeout() throws SQLException { - checkOpen(); - try { - return Jdbc41Bridge.getNetworkTimeout(connection); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public String getSchema() throws SQLException { - checkOpen(); - if (cacheState && cachedSchema != null) { - return cachedSchema; - } - try { - cachedSchema = Jdbc41Bridge.getSchema(connection); - return cachedSchema; - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public int getTransactionIsolation() throws SQLException { - checkOpen(); - try { - return connection.getTransactionIsolation(); - } catch (final SQLException e) { - handleException(e); - return -1; - } - } - - @Override - public Map> getTypeMap() throws SQLException { - checkOpen(); - try { - return connection.getTypeMap(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public SQLWarning getWarnings() throws SQLException { - checkOpen(); - try { - return connection.getWarnings(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - /** - * Handles the given exception by throwing it. - * - * @param e the exception to throw. - * @throws SQLException the exception to throw. - */ - protected void handleException(final SQLException e) throws SQLException { - throw e; - } - - /** - * Handles the given {@code SQLException}. - * - * @param The throwable type. - * @param e The SQLException - * @return the given {@code SQLException} - * @since 2.7.0 - */ - protected T handleExceptionNoThrow(final T e) { - return e; - } - - /** - * Initializes the given statement with this connection's settings. - * - * @param The DelegatingStatement type. - * @param delegatingStatement The DelegatingStatement to initialize. - * @return The given DelegatingStatement. - * @throws SQLException if a database access error occurs, this method is called on a closed Statement. - */ - private T init(final T delegatingStatement) throws SQLException { - if (defaultQueryTimeoutDuration != null && defaultQueryTimeoutDuration.getSeconds() != delegatingStatement.getQueryTimeout()) { - delegatingStatement.setQueryTimeout((int) defaultQueryTimeoutDuration.getSeconds()); - } - return delegatingStatement; - } - - /** - * Compares innermost delegate to the given connection. - * - * @param c - * connection to compare innermost delegate with - * @return true if innermost delegate equals {@code c} - */ - @SuppressWarnings("resource") - public boolean innermostDelegateEquals(final Connection c) { - final Connection innerCon = getInnermostDelegateInternal(); - if (innerCon == null) { - return c == null; - } - return innerCon.equals(c); - } - - @Override - public boolean isClosed() throws SQLException { - return closed || connection == null || connection.isClosed(); - } - - /** - * Tests the raw internal closed state. - * - * @return the raw internal closed state. - */ - protected boolean isClosedInternal() { - return closed; - } - - @Override - public boolean isReadOnly() throws SQLException { - checkOpen(); - if (cacheState && cachedReadOnly != null) { - return cachedReadOnly; - } - try { - cachedReadOnly = connection.isReadOnly(); - return cachedReadOnly; - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - /** - * Tests if the connection has not been closed and is still valid. - * - * @param timeout The duration to wait for the database operation used to validate the connection to complete. - * @return See {@link Connection#isValid(int)}. - * @throws SQLException See {@link Connection#isValid(int)}. - * @see Connection#isValid(int) - * @since 2.10.0 - */ - public boolean isValid(final Duration timeout) throws SQLException { - if (isClosed()) { - return false; - } - try { - return connection.isValid((int) timeout.getSeconds()); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - /** - * @deprecated Use {@link #isValid(Duration)}. - */ - @Override - @Deprecated - public boolean isValid(final int timeoutSeconds) throws SQLException { - return isValid(Duration.ofSeconds(timeoutSeconds)); - } - - @Override - public boolean isWrapperFor(final Class iface) throws SQLException { - if (iface.isAssignableFrom(getClass())) { - return true; - } - if (iface.isAssignableFrom(connection.getClass())) { - return true; - } - return connection.isWrapperFor(iface); - } - - @Override - public String nativeSQL(final String sql) throws SQLException { - checkOpen(); - try { - return connection.nativeSQL(sql); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - /** - * Clears the list of objects being traced by this object. - * - * @throws SQLException Thrown if not all traced objects were closed. - */ - protected void passivate() throws SQLException { - // The JDBC specification requires that a Connection close any open - // Statements when it is closed. - // DBCP-288. Not all the traced objects will be statements - final List traceList = getTrace(); - if (!Utils.isEmpty(traceList)) { - final List thrownList = new ArrayList<>(); - traceList.forEach(trace -> trace.close(thrownList::add)); - clearTrace(); - if (!thrownList.isEmpty()) { - throw new SQLExceptionList(thrownList); - } - } - setLastUsed(Instant.EPOCH); - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public CallableStatement prepareCall(final String sql) throws SQLException { - checkOpen(); - try { - return init(new DelegatingCallableStatement(this, connection.prepareCall(sql))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - checkOpen(); - try { - return init(new DelegatingCallableStatement(this, - connection.prepareCall(sql, resultSetType, resultSetConcurrency))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - checkOpen(); - try { - return init(new DelegatingCallableStatement(this, - connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql) throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, - connection.prepareStatement(sql, resultSetType, resultSetConcurrency))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, - connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { - checkOpen(); - try { - return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames))); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public void releaseSavepoint(final Savepoint savepoint) throws SQLException { - checkOpen(); - try { - connection.releaseSavepoint(savepoint); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void rollback() throws SQLException { - checkOpen(); - try { - connection.rollback(); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void rollback(final Savepoint savepoint) throws SQLException { - checkOpen(); - try { - connection.rollback(savepoint); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setAutoCommit(final boolean autoCommit) throws SQLException { - checkOpen(); - try { - connection.setAutoCommit(autoCommit); - if (cacheState) { - cachedAutoCommit = connection.getAutoCommit(); - } - } catch (final SQLException e) { - cachedAutoCommit = null; - handleException(e); - } - } - - /** - * Sets whether to cache properties. The cached properties are: - *
    - *
  • auto-commit
  • - *
  • catalog
  • - *
  • schema
  • - *
  • read-only
  • - *
- * - * @param cacheState The new value for the state caching flag - */ - public void setCacheState(final boolean cacheState) { - this.cacheState = cacheState; - } - - @Override - public void setCatalog(final String catalog) throws SQLException { - checkOpen(); - try { - connection.setCatalog(catalog); - if (cacheState) { - cachedCatalog = connection.getCatalog(); - } - } catch (final SQLException e) { - cachedCatalog = null; - handleException(e); - } - } - - @Override - public void setClientInfo(final Properties properties) throws SQLClientInfoException { - try { - checkOpen(); - connection.setClientInfo(properties); - } catch (final SQLClientInfoException e) { - throw e; - } catch (final SQLException e) { - throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); - } - } - - @Override - public void setClientInfo(final String name, final String value) throws SQLClientInfoException { - try { - checkOpen(); - connection.setClientInfo(name, value); - } catch (final SQLClientInfoException e) { - throw e; - } catch (final SQLException e) { - throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); - } - } - - /** - * Sets the raw internal closed state. - * - * @param closed the raw internal closed state. - */ - protected void setClosedInternal(final boolean closed) { - this.closed = closed; - } - - /** - * Sets the default query timeout that will be used for {@link Statement}s created from this connection. - * {@code null} means that the driver default will be used. - * - * @param defaultQueryTimeoutDuration - * the new query timeout limit Duration; zero means there is no limit. - * @since 2.10.0 - */ - public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; - } - - /** - * Sets the default query timeout that will be used for {@link Statement}s created from this connection. - * {@code null} means that the driver default will be used. - * - * @param defaultQueryTimeoutSeconds - * the new query timeout limit in seconds; zero means there is no limit. - * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. - */ - @Deprecated - public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); - } - - /** - * Sets my delegate. - * - * @param connection - * my delegate, may be null. - */ - public void setDelegate(final C connection) { - this.connection = connection; - } - - @Override - public void setHoldability(final int holdability) throws SQLException { - checkOpen(); - try { - connection.setHoldability(holdability); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException { - checkOpen(); - try { - Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setReadOnly(final boolean readOnly) throws SQLException { - checkOpen(); - try { - connection.setReadOnly(readOnly); - if (cacheState) { - cachedReadOnly = connection.isReadOnly(); - } - } catch (final SQLException e) { - cachedReadOnly = null; - handleException(e); - } - } - - @Override - public Savepoint setSavepoint() throws SQLException { - checkOpen(); - try { - return connection.setSavepoint(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public Savepoint setSavepoint(final String name) throws SQLException { - checkOpen(); - try { - return connection.setSavepoint(name); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public void setSchema(final String schema) throws SQLException { - checkOpen(); - try { - Jdbc41Bridge.setSchema(connection, schema); - if (cacheState) { - cachedSchema = Jdbc41Bridge.getSchema(connection); - } - } catch (final SQLException e) { - cachedSchema = null; - handleException(e); - } - } - - @Override - public void setTransactionIsolation(final int level) throws SQLException { - checkOpen(); - try { - connection.setTransactionIsolation(level); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setTypeMap(final Map> map) throws SQLException { - checkOpen(); - try { - connection.setTypeMap(map); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Returns a string representation of the metadata associated with the innermost delegate connection. - */ - @SuppressWarnings("resource") - @Override - public synchronized String toString() { - String str = null; - - final Connection conn = this.getInnermostDelegateInternal(); - if (conn != null) { - try { - if (conn.isClosed()) { - str = "connection is closed"; - } else { - final StringBuilder sb = new StringBuilder(); - sb.append(hashCode()); - final DatabaseMetaData meta = conn.getMetaData(); - if (meta != null) { - sb.append(", URL="); - sb.append(meta.getURL()); - sb.append(", "); - sb.append(meta.getDriverName()); - str = sb.toString(); - } - } - } catch (final SQLException ignored) { - // Ignore - } - } - return str != null ? str : super.toString(); - } - - @Override - public T unwrap(final Class iface) throws SQLException { - if (iface.isAssignableFrom(getClass())) { - return iface.cast(this); - } - if (iface.isAssignableFrom(connection.getClass())) { - return iface.cast(connection); - } - return connection.unwrap(iface); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.ClientInfoStatus; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +import org.apache.commons.dbcp2.managed.ManagedConnection; + +/** + * A base delegating implementation of {@link Connection}. + *

+ * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active, + * and call the corresponding method on the "delegate" provided in my constructor. + *

+ *

+ * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking + * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of + * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout. + *

+ * + * @param + * the Connection type + * + * @since 2.0 + */ +public class DelegatingConnection extends AbandonedTrace implements Connection { + + private static final Map EMPTY_FAILED_PROPERTIES = Collections + .emptyMap(); + + /** My delegate {@link Connection}. */ + private volatile C connection; + + private volatile boolean closed; + + private boolean cacheState = true; + private Boolean cachedAutoCommit; + private Boolean cachedReadOnly; + private String cachedCatalog; + private String cachedSchema; + private Duration defaultQueryTimeoutDuration; + + /** + * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool. + * + * @param connection the {@link Connection} to delegate all calls to, may be null (see {@link ManagedConnection}). + */ + public DelegatingConnection(final C connection) { + this.connection = connection; + } + + @Override + public void abort(final Executor executor) throws SQLException { + try { + Jdbc41Bridge.abort(connection, executor); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Marks this instance as used and delegates to a wrapped {@link DelegatingConnection#activate()}. + */ + protected void activate() { + closed = false; + setLastUsed(); + if (connection instanceof DelegatingConnection) { + ((DelegatingConnection) connection).activate(); + } + } + + /** + * Throws a SQLException if this connection is not open. + * + * @throws SQLException Thrown if this connection is not open. + */ + protected void checkOpen() throws SQLException { + if (closed) { + if (null != connection) { + String label; + try { + label = connection.toString(); + } catch (final Exception e) { + // leave label empty + label = ""; + } + throw new SQLException("Connection " + label + " is closed."); + } + throw new SQLException("Connection is null."); + } + } + + /** + * Clears the cached state. Call when you know that the underlying connection may have been accessed + * directly. + */ + public void clearCachedState() { + cachedAutoCommit = null; + cachedReadOnly = null; + cachedSchema = null; + cachedCatalog = null; + if (connection instanceof DelegatingConnection) { + ((DelegatingConnection) connection).clearCachedState(); + } + } + + @Override + public void clearWarnings() throws SQLException { + checkOpen(); + try { + connection.clearWarnings(); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that + * override this method must: + *
    + *
  1. Call {@link #passivate()}
  2. + *
  3. Call close (or the equivalent appropriate action) on the wrapped connection
  4. + *
  5. Set {@code closed} to {@code false}
  6. + *
+ */ + @Override + public void close() throws SQLException { + if (!closed) { + closeInternal(); + } + } + + protected final void closeInternal() throws SQLException { + try { + passivate(); + } finally { + if (connection != null) { + boolean connectionIsClosed; + try { + connectionIsClosed = connection.isClosed(); + } catch (final SQLException e) { + // not sure what the state is, so assume the connection is open. + connectionIsClosed = false; + } + try { + // DBCP-512: Avoid exceptions when closing a connection in multi-threaded use case. + // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when + // closing from multiple threads. + if (!connectionIsClosed) { + connection.close(); + } + } finally { + closed = true; + } + } else { + closed = true; + } + } + } + + @Override + public void commit() throws SQLException { + checkOpen(); + try { + connection.commit(); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException { + checkOpen(); + try { + return connection.createArrayOf(typeName, elements); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public Blob createBlob() throws SQLException { + checkOpen(); + try { + return connection.createBlob(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public Clob createClob() throws SQLException { + checkOpen(); + try { + return connection.createClob(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public NClob createNClob() throws SQLException { + checkOpen(); + try { + return connection.createNClob(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public SQLXML createSQLXML() throws SQLException { + checkOpen(); + try { + return connection.createSQLXML(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public Statement createStatement() throws SQLException { + checkOpen(); + try { + return init(new DelegatingStatement(this, connection.createStatement())); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException { + checkOpen(); + try { + return init(new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return init(new DelegatingStatement(this, + connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException { + checkOpen(); + try { + return connection.createStruct(typeName, attributes); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public boolean getAutoCommit() throws SQLException { + checkOpen(); + if (cacheState && cachedAutoCommit != null) { + return cachedAutoCommit; + } + try { + cachedAutoCommit = connection.getAutoCommit(); + return cachedAutoCommit; + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + /** + * Gets whether to cache properties. The cached properties are: + *
    + *
  • auto-commit
  • + *
  • catalog
  • + *
  • schema
  • + *
  • read-only
  • + *
+ * + * @return the state caching flag + */ + public boolean getCacheState() { + return cacheState; + } + + @Override + public String getCatalog() throws SQLException { + checkOpen(); + if (cacheState && cachedCatalog != null) { + return cachedCatalog; + } + try { + cachedCatalog = connection.getCatalog(); + return cachedCatalog; + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public Properties getClientInfo() throws SQLException { + checkOpen(); + try { + return connection.getClientInfo(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public String getClientInfo(final String name) throws SQLException { + checkOpen(); + try { + return connection.getClientInfo(name); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + /** + * Gets the default query timeout that will be used for {@link Statement}s created from this connection. + * {@code null} means that the driver default will be used. + * + * @return query timeout limit in seconds; zero means there is no limit. + * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. + */ + @Deprecated + public Integer getDefaultQueryTimeout() { + return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); + } + + /** + * Gets the default query timeout that will be used for {@link Statement}s created from this connection. + * {@code null} means that the driver default will be used. + * + * @return query timeout limit; zero means there is no limit. + * @since 2.10.0 + */ + public Duration getDefaultQueryTimeoutDuration() { + return defaultQueryTimeoutDuration; + } + + /** + * Returns my underlying {@link Connection}. + * + * @return my underlying {@link Connection}. + */ + public C getDelegate() { + return getDelegateInternal(); + } + + /** + * Gets the delegate connection. + * + * @return the delegate connection. + */ + protected final C getDelegateInternal() { + return connection; + } + + @Override + public int getHoldability() throws SQLException { + checkOpen(); + try { + return connection.getHoldability(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + /** + * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively + * invokes this method on my delegate. + *

+ * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when + * no non-{@code DelegatingConnection} delegate can be found by traversing this chain. + *

+ *

+ * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain + * a "genuine" {@link Connection}. + *

+ * + * @return innermost delegate. + */ + public Connection getInnermostDelegate() { + return getInnermostDelegateInternal(); + } + + /** + * Although this method is public, it is part of the internal API and should not be used by clients. The signature + * of this method may change at any time including in ways that break backwards compatibility. + * + * @return innermost delegate. + */ + @SuppressWarnings("resource") + public final Connection getInnermostDelegateInternal() { + Connection conn = connection; + while (conn instanceof DelegatingConnection) { + conn = ((DelegatingConnection) conn).getDelegateInternal(); + if (this == conn) { + return null; + } + } + return conn; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + checkOpen(); + try { + return new DelegatingDatabaseMetaData(this, connection.getMetaData()); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public int getNetworkTimeout() throws SQLException { + checkOpen(); + try { + return Jdbc41Bridge.getNetworkTimeout(connection); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public String getSchema() throws SQLException { + checkOpen(); + if (cacheState && cachedSchema != null) { + return cachedSchema; + } + try { + cachedSchema = Jdbc41Bridge.getSchema(connection); + return cachedSchema; + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public int getTransactionIsolation() throws SQLException { + checkOpen(); + try { + return connection.getTransactionIsolation(); + } catch (final SQLException e) { + handleException(e); + return -1; + } + } + + @Override + public Map> getTypeMap() throws SQLException { + checkOpen(); + try { + return connection.getTypeMap(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public SQLWarning getWarnings() throws SQLException { + checkOpen(); + try { + return connection.getWarnings(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + /** + * Handles the given exception by throwing it. + * + * @param e the exception to throw. + * @throws SQLException the exception to throw. + */ + protected void handleException(final SQLException e) throws SQLException { + throw e; + } + + /** + * Handles the given {@code SQLException}. + * + * @param The throwable type. + * @param e The SQLException + * @return the given {@code SQLException} + * @since 2.7.0 + */ + protected T handleExceptionNoThrow(final T e) { + return e; + } + + /** + * Initializes the given statement with this connection's settings. + * + * @param The DelegatingStatement type. + * @param delegatingStatement The DelegatingStatement to initialize. + * @return The given DelegatingStatement. + * @throws SQLException if a database access error occurs, this method is called on a closed Statement. + */ + private T init(final T delegatingStatement) throws SQLException { + if (defaultQueryTimeoutDuration != null && defaultQueryTimeoutDuration.getSeconds() != delegatingStatement.getQueryTimeout()) { + delegatingStatement.setQueryTimeout((int) defaultQueryTimeoutDuration.getSeconds()); + } + return delegatingStatement; + } + + /** + * Compares innermost delegate to the given connection. + * + * @param c + * connection to compare innermost delegate with + * @return true if innermost delegate equals {@code c} + */ + @SuppressWarnings("resource") + public boolean innermostDelegateEquals(final Connection c) { + final Connection innerCon = getInnermostDelegateInternal(); + if (innerCon == null) { + return c == null; + } + return innerCon.equals(c); + } + + @Override + public boolean isClosed() throws SQLException { + return closed || connection == null || connection.isClosed(); + } + + /** + * Tests the raw internal closed state. + * + * @return the raw internal closed state. + */ + protected boolean isClosedInternal() { + return closed; + } + + @Override + public boolean isReadOnly() throws SQLException { + checkOpen(); + if (cacheState && cachedReadOnly != null) { + return cachedReadOnly; + } + try { + cachedReadOnly = connection.isReadOnly(); + return cachedReadOnly; + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + /** + * Tests if the connection has not been closed and is still valid. + * + * @param timeout The duration to wait for the database operation used to validate the connection to complete. + * @return See {@link Connection#isValid(int)}. + * @throws SQLException See {@link Connection#isValid(int)}. + * @see Connection#isValid(int) + * @since 2.10.0 + */ + public boolean isValid(final Duration timeout) throws SQLException { + if (isClosed()) { + return false; + } + try { + return connection.isValid((int) timeout.getSeconds()); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + /** + * @deprecated Use {@link #isValid(Duration)}. + */ + @Override + @Deprecated + public boolean isValid(final int timeoutSeconds) throws SQLException { + return isValid(Duration.ofSeconds(timeoutSeconds)); + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + if (iface.isAssignableFrom(getClass())) { + return true; + } + if (iface.isAssignableFrom(connection.getClass())) { + return true; + } + return connection.isWrapperFor(iface); + } + + @Override + public String nativeSQL(final String sql) throws SQLException { + checkOpen(); + try { + return connection.nativeSQL(sql); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + /** + * Clears the list of objects being traced by this object. + * + * @throws SQLException Thrown if not all traced objects were closed. + */ + protected void passivate() throws SQLException { + // The JDBC specification requires that a Connection close any open + // Statements when it is closed. + // DBCP-288. Not all the traced objects will be statements + final List traceList = getTrace(); + if (!Utils.isEmpty(traceList)) { + final List thrownList = new ArrayList<>(); + traceList.forEach(trace -> trace.close(thrownList::add)); + clearTrace(); + if (!thrownList.isEmpty()) { + throw new SQLExceptionList(thrownList); + } + } + setLastUsed(Instant.EPOCH); + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public CallableStatement prepareCall(final String sql) throws SQLException { + checkOpen(); + try { + return init(new DelegatingCallableStatement(this, connection.prepareCall(sql))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + checkOpen(); + try { + return init(new DelegatingCallableStatement(this, + connection.prepareCall(sql, resultSetType, resultSetConcurrency))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return init(new DelegatingCallableStatement(this, + connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, + connection.prepareStatement(sql, resultSetType, resultSetConcurrency))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, + connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + checkOpen(); + try { + return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames))); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public void releaseSavepoint(final Savepoint savepoint) throws SQLException { + checkOpen(); + try { + connection.releaseSavepoint(savepoint); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void rollback() throws SQLException { + checkOpen(); + try { + connection.rollback(); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void rollback(final Savepoint savepoint) throws SQLException { + checkOpen(); + try { + connection.rollback(savepoint); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setAutoCommit(final boolean autoCommit) throws SQLException { + checkOpen(); + try { + connection.setAutoCommit(autoCommit); + if (cacheState) { + cachedAutoCommit = connection.getAutoCommit(); + } + } catch (final SQLException e) { + cachedAutoCommit = null; + handleException(e); + } + } + + /** + * Sets whether to cache properties. The cached properties are: + *
    + *
  • auto-commit
  • + *
  • catalog
  • + *
  • schema
  • + *
  • read-only
  • + *
+ * + * @param cacheState The new value for the state caching flag + */ + public void setCacheState(final boolean cacheState) { + this.cacheState = cacheState; + } + + @Override + public void setCatalog(final String catalog) throws SQLException { + checkOpen(); + try { + connection.setCatalog(catalog); + if (cacheState) { + cachedCatalog = connection.getCatalog(); + } + } catch (final SQLException e) { + cachedCatalog = null; + handleException(e); + } + } + + @Override + public void setClientInfo(final Properties properties) throws SQLClientInfoException { + try { + checkOpen(); + connection.setClientInfo(properties); + } catch (final SQLClientInfoException e) { + throw e; + } catch (final SQLException e) { + throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); + } + } + + @Override + public void setClientInfo(final String name, final String value) throws SQLClientInfoException { + try { + checkOpen(); + connection.setClientInfo(name, value); + } catch (final SQLClientInfoException e) { + throw e; + } catch (final SQLException e) { + throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); + } + } + + /** + * Sets the raw internal closed state. + * + * @param closed the raw internal closed state. + */ + protected void setClosedInternal(final boolean closed) { + this.closed = closed; + } + + /** + * Sets the default query timeout that will be used for {@link Statement}s created from this connection. + * {@code null} means that the driver default will be used. + * + * @param defaultQueryTimeoutDuration + * the new query timeout limit Duration; zero means there is no limit. + * @since 2.10.0 + */ + public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; + } + + /** + * Sets the default query timeout that will be used for {@link Statement}s created from this connection. + * {@code null} means that the driver default will be used. + * + * @param defaultQueryTimeoutSeconds + * the new query timeout limit in seconds; zero means there is no limit. + * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. + */ + @Deprecated + public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); + } + + /** + * Sets my delegate. + * + * @param connection + * my delegate, may be null. + */ + public void setDelegate(final C connection) { + this.connection = connection; + } + + @Override + public void setHoldability(final int holdability) throws SQLException { + checkOpen(); + try { + connection.setHoldability(holdability); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException { + checkOpen(); + try { + Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setReadOnly(final boolean readOnly) throws SQLException { + checkOpen(); + try { + connection.setReadOnly(readOnly); + if (cacheState) { + cachedReadOnly = connection.isReadOnly(); + } + } catch (final SQLException e) { + cachedReadOnly = null; + handleException(e); + } + } + + @Override + public Savepoint setSavepoint() throws SQLException { + checkOpen(); + try { + return connection.setSavepoint(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public Savepoint setSavepoint(final String name) throws SQLException { + checkOpen(); + try { + return connection.setSavepoint(name); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public void setSchema(final String schema) throws SQLException { + checkOpen(); + try { + Jdbc41Bridge.setSchema(connection, schema); + if (cacheState) { + cachedSchema = Jdbc41Bridge.getSchema(connection); + } + } catch (final SQLException e) { + cachedSchema = null; + handleException(e); + } + } + + @Override + public void setTransactionIsolation(final int level) throws SQLException { + checkOpen(); + try { + connection.setTransactionIsolation(level); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setTypeMap(final Map> map) throws SQLException { + checkOpen(); + try { + connection.setTypeMap(map); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Returns a string representation of the metadata associated with the innermost delegate connection. + */ + @SuppressWarnings("resource") + @Override + public synchronized String toString() { + String str = null; + + final Connection conn = this.getInnermostDelegateInternal(); + if (conn != null) { + try { + if (conn.isClosed()) { + str = "connection is closed"; + } else { + final StringBuilder sb = new StringBuilder(); + sb.append(hashCode()); + final DatabaseMetaData meta = conn.getMetaData(); + if (meta != null) { + sb.append(", URL="); + sb.append(meta.getURL()); + sb.append(", "); + sb.append(meta.getDriverName()); + str = sb.toString(); + } + } + } catch (final SQLException ignored) { + // Ignore + } + } + return str != null ? str : super.toString(); + } + + @Override + public T unwrap(final Class iface) throws SQLException { + if (iface.isAssignableFrom(getClass())) { + return iface.cast(this); + } + if (iface.isAssignableFrom(connection.getClass())) { + return iface.cast(connection); + } + return connection.unwrap(iface); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java b/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java index 68964cf439..527d4cc361 100644 --- a/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java +++ b/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java @@ -1,716 +1,716 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Date; -import java.sql.NClob; -import java.sql.PreparedStatement; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.RowId; -import java.sql.SQLException; -import java.sql.SQLType; -import java.sql.SQLXML; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Objects; - -/** - * A base delegating implementation of {@link PreparedStatement}. - *

- * All of the methods from the {@link PreparedStatement} interface simply check to see that the - * {@link PreparedStatement} is active, and call the corresponding method on the "delegate" provided in my constructor. - *

- * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the - * Statement ensures that the Connection which created it can close any open Statement's on Connection close. - * - * @since 2.0 - */ -public class DelegatingPreparedStatement extends DelegatingStatement implements PreparedStatement { - - /** - * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code - * which created it. - * - * @param statement - * the {@link PreparedStatement} to delegate all calls to. - * @param connection - * the {@link DelegatingConnection} that created this statement. - */ - public DelegatingPreparedStatement(final DelegatingConnection connection, final PreparedStatement statement) { - super(connection, statement); - } - - @Override - public void addBatch() throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().addBatch(); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void clearParameters() throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().clearParameters(); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public boolean execute() throws SQLException { - checkOpen(); - if (getConnectionInternal() != null) { - getConnectionInternal().setLastUsed(); - } - try { - return getDelegatePreparedStatement().execute(); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long executeLargeUpdate() throws SQLException { - checkOpen(); - try { - return getDelegatePreparedStatement().executeLargeUpdate(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public ResultSet executeQuery() throws SQLException { - checkOpen(); - if (getConnectionInternal() != null) { - getConnectionInternal().setLastUsed(); - } - try { - return DelegatingResultSet.wrapResultSet(this, getDelegatePreparedStatement().executeQuery()); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - @Override - public int executeUpdate() throws SQLException { - checkOpen(); - if (getConnectionInternal() != null) { - getConnectionInternal().setLastUsed(); - } - try { - return getDelegatePreparedStatement().executeUpdate(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - private PreparedStatement getDelegatePreparedStatement() { - return (PreparedStatement) getDelegate(); - } - - @Override - public ResultSetMetaData getMetaData() throws SQLException { - checkOpen(); - try { - return getDelegatePreparedStatement().getMetaData(); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - @Override - public java.sql.ParameterMetaData getParameterMetaData() throws SQLException { - checkOpen(); - try { - return getDelegatePreparedStatement().getParameterMetaData(); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - protected void prepareToReturn() throws SQLException { - setClosedInternal(true); - removeThisTrace(getConnectionInternal()); - - // The JDBC spec requires that a statement close any open - // ResultSet's when it is closed. - // FIXME The PreparedStatement we're wrapping should handle this for us. - // See DBCP-10 for what could happen when ResultSets are closed twice. - final List traceList = getTrace(); - if (traceList != null) { - final List thrownList = new ArrayList<>(); - traceList.forEach(trace -> trace.close(thrownList::add)); - clearTrace(); - if (!thrownList.isEmpty()) { - throw new SQLExceptionList(thrownList); - } - } - - super.passivate(); - } - - @Override - public void setArray(final int i, final Array x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setArray(i, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setAsciiStream(final int parameterIndex, final InputStream inputStream) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setAsciiStream(parameterIndex, inputStream); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setAsciiStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setAsciiStream(parameterIndex, x, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setAsciiStream(final int parameterIndex, final InputStream inputStream, final long length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setAsciiStream(parameterIndex, inputStream, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBigDecimal(final int parameterIndex, final BigDecimal x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBigDecimal(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBinaryStream(final int parameterIndex, final InputStream inputStream) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBinaryStream(parameterIndex, inputStream); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBinaryStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBinaryStream(parameterIndex, x, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBinaryStream(final int parameterIndex, final InputStream inputStream, final long length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBinaryStream(parameterIndex, inputStream, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBlob(final int i, final Blob x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBlob(i, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBlob(final int parameterIndex, final InputStream inputStream) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBlob(parameterIndex, inputStream); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBlob(final int parameterIndex, final InputStream inputStream, final long length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBlob(parameterIndex, inputStream, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBoolean(final int parameterIndex, final boolean x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBoolean(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setByte(final int parameterIndex, final byte x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setByte(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setBytes(final int parameterIndex, final byte[] x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setBytes(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setCharacterStream(final int parameterIndex, final Reader reader) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setCharacterStream(final int parameterIndex, final Reader reader, final int length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setCharacterStream(final int parameterIndex, final Reader reader, final long length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setClob(final int i, final Clob x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setClob(i, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setClob(final int parameterIndex, final Reader reader) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setClob(parameterIndex, reader); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setClob(final int parameterIndex, final Reader reader, final long length) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setClob(parameterIndex, reader, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setDate(final int parameterIndex, final Date x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setDate(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setDate(final int parameterIndex, final Date x, final Calendar cal) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setDate(parameterIndex, x, cal); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setDouble(final int parameterIndex, final double x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setDouble(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setFloat(final int parameterIndex, final float x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setFloat(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setInt(final int parameterIndex, final int x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setInt(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setLong(final int parameterIndex, final long x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setLong(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNCharacterStream(final int parameterIndex, final Reader reader) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNCharacterStream(parameterIndex, reader); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNCharacterStream(final int parameterIndex, final Reader value, final long length) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNCharacterStream(parameterIndex, value, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNClob(final int parameterIndex, final NClob value) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNClob(parameterIndex, value); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNClob(final int parameterIndex, final Reader reader) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNClob(parameterIndex, reader); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNClob(final int parameterIndex, final Reader reader, final long length) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNClob(parameterIndex, reader, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNString(final int parameterIndex, final String value) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNString(parameterIndex, value); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNull(final int parameterIndex, final int sqlType) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNull(parameterIndex, sqlType); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setNull(final int paramIndex, final int sqlType, final String typeName) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setNull(paramIndex, sqlType, typeName); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setObject(final int parameterIndex, final Object x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setObject(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setObject(final int parameterIndex, final Object x, final int targetSqlType) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setObject(final int parameterIndex, final Object x, final int targetSqlType, final int scale) - throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType, scale); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * @since 2.5.0 - */ - @Override - public void setObject(final int parameterIndex, final Object x, final SQLType targetSqlType) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * @since 2.5.0 - */ - @Override - public void setObject(final int parameterIndex, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType, scaleOrLength); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setRef(final int i, final Ref x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setRef(i, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setRowId(final int parameterIndex, final RowId value) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setRowId(parameterIndex, value); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setShort(final int parameterIndex, final short x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setShort(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setSQLXML(final int parameterIndex, final SQLXML value) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setSQLXML(parameterIndex, value); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setString(final int parameterIndex, final String x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setString(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setTime(final int parameterIndex, final Time x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setTime(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setTime(final int parameterIndex, final Time x, final Calendar cal) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setTime(parameterIndex, x, cal); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setTimestamp(final int parameterIndex, final Timestamp x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setTimestamp(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setTimestamp(final int parameterIndex, final Timestamp x, final Calendar cal) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setTimestamp(parameterIndex, x, cal); - } catch (final SQLException e) { - handleException(e); - } - } - - /** @deprecated Use setAsciiStream(), setCharacterStream() or setNCharacterStream() */ - @Deprecated - @Override - public void setUnicodeStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setUnicodeStream(parameterIndex, x, length); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setURL(final int parameterIndex, final java.net.URL x) throws SQLException { - checkOpen(); - try { - getDelegatePreparedStatement().setURL(parameterIndex, x); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Returns a String representation of this object. - * - * @return String - */ - @SuppressWarnings("resource") - @Override - public synchronized String toString() { - return Objects.toString(getDelegate(), "NULL"); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Objects; + +/** + * A base delegating implementation of {@link PreparedStatement}. + *

+ * All of the methods from the {@link PreparedStatement} interface simply check to see that the + * {@link PreparedStatement} is active, and call the corresponding method on the "delegate" provided in my constructor. + *

+ * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the + * Statement ensures that the Connection which created it can close any open Statement's on Connection close. + * + * @since 2.0 + */ +public class DelegatingPreparedStatement extends DelegatingStatement implements PreparedStatement { + + /** + * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code + * which created it. + * + * @param statement + * the {@link PreparedStatement} to delegate all calls to. + * @param connection + * the {@link DelegatingConnection} that created this statement. + */ + public DelegatingPreparedStatement(final DelegatingConnection connection, final PreparedStatement statement) { + super(connection, statement); + } + + @Override + public void addBatch() throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().addBatch(); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void clearParameters() throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().clearParameters(); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public boolean execute() throws SQLException { + checkOpen(); + if (getConnectionInternal() != null) { + getConnectionInternal().setLastUsed(); + } + try { + return getDelegatePreparedStatement().execute(); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long executeLargeUpdate() throws SQLException { + checkOpen(); + try { + return getDelegatePreparedStatement().executeLargeUpdate(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public ResultSet executeQuery() throws SQLException { + checkOpen(); + if (getConnectionInternal() != null) { + getConnectionInternal().setLastUsed(); + } + try { + return DelegatingResultSet.wrapResultSet(this, getDelegatePreparedStatement().executeQuery()); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + @Override + public int executeUpdate() throws SQLException { + checkOpen(); + if (getConnectionInternal() != null) { + getConnectionInternal().setLastUsed(); + } + try { + return getDelegatePreparedStatement().executeUpdate(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + private PreparedStatement getDelegatePreparedStatement() { + return (PreparedStatement) getDelegate(); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + checkOpen(); + try { + return getDelegatePreparedStatement().getMetaData(); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + @Override + public java.sql.ParameterMetaData getParameterMetaData() throws SQLException { + checkOpen(); + try { + return getDelegatePreparedStatement().getParameterMetaData(); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + protected void prepareToReturn() throws SQLException { + setClosedInternal(true); + removeThisTrace(getConnectionInternal()); + + // The JDBC spec requires that a statement close any open + // ResultSet's when it is closed. + // FIXME The PreparedStatement we're wrapping should handle this for us. + // See DBCP-10 for what could happen when ResultSets are closed twice. + final List traceList = getTrace(); + if (traceList != null) { + final List thrownList = new ArrayList<>(); + traceList.forEach(trace -> trace.close(thrownList::add)); + clearTrace(); + if (!thrownList.isEmpty()) { + throw new SQLExceptionList(thrownList); + } + } + + super.passivate(); + } + + @Override + public void setArray(final int i, final Array x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setArray(i, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setAsciiStream(final int parameterIndex, final InputStream inputStream) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setAsciiStream(parameterIndex, inputStream); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setAsciiStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setAsciiStream(parameterIndex, x, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setAsciiStream(final int parameterIndex, final InputStream inputStream, final long length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setAsciiStream(parameterIndex, inputStream, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBigDecimal(final int parameterIndex, final BigDecimal x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBigDecimal(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBinaryStream(final int parameterIndex, final InputStream inputStream) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBinaryStream(parameterIndex, inputStream); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBinaryStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBinaryStream(parameterIndex, x, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBinaryStream(final int parameterIndex, final InputStream inputStream, final long length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBinaryStream(parameterIndex, inputStream, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBlob(final int i, final Blob x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBlob(i, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBlob(final int parameterIndex, final InputStream inputStream) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBlob(parameterIndex, inputStream); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBlob(final int parameterIndex, final InputStream inputStream, final long length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBlob(parameterIndex, inputStream, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBoolean(final int parameterIndex, final boolean x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBoolean(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setByte(final int parameterIndex, final byte x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setByte(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setBytes(final int parameterIndex, final byte[] x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setBytes(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setCharacterStream(final int parameterIndex, final Reader reader) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setCharacterStream(final int parameterIndex, final Reader reader, final int length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setCharacterStream(final int parameterIndex, final Reader reader, final long length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setCharacterStream(parameterIndex, reader, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setClob(final int i, final Clob x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setClob(i, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setClob(final int parameterIndex, final Reader reader) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setClob(parameterIndex, reader); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setClob(final int parameterIndex, final Reader reader, final long length) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setClob(parameterIndex, reader, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setDate(final int parameterIndex, final Date x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setDate(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setDate(final int parameterIndex, final Date x, final Calendar cal) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setDate(parameterIndex, x, cal); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setDouble(final int parameterIndex, final double x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setDouble(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setFloat(final int parameterIndex, final float x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setFloat(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setInt(final int parameterIndex, final int x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setInt(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setLong(final int parameterIndex, final long x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setLong(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNCharacterStream(final int parameterIndex, final Reader reader) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNCharacterStream(parameterIndex, reader); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNCharacterStream(final int parameterIndex, final Reader value, final long length) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNCharacterStream(parameterIndex, value, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNClob(final int parameterIndex, final NClob value) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNClob(parameterIndex, value); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNClob(final int parameterIndex, final Reader reader) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNClob(parameterIndex, reader); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNClob(final int parameterIndex, final Reader reader, final long length) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNClob(parameterIndex, reader, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNString(final int parameterIndex, final String value) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNString(parameterIndex, value); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNull(final int parameterIndex, final int sqlType) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNull(parameterIndex, sqlType); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setNull(final int paramIndex, final int sqlType, final String typeName) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setNull(paramIndex, sqlType, typeName); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setObject(final int parameterIndex, final Object x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setObject(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setObject(final int parameterIndex, final Object x, final int targetSqlType) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setObject(final int parameterIndex, final Object x, final int targetSqlType, final int scale) + throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType, scale); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * @since 2.5.0 + */ + @Override + public void setObject(final int parameterIndex, final Object x, final SQLType targetSqlType) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * @since 2.5.0 + */ + @Override + public void setObject(final int parameterIndex, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setRef(final int i, final Ref x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setRef(i, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setRowId(final int parameterIndex, final RowId value) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setRowId(parameterIndex, value); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setShort(final int parameterIndex, final short x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setShort(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setSQLXML(final int parameterIndex, final SQLXML value) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setSQLXML(parameterIndex, value); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setString(final int parameterIndex, final String x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setString(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setTime(final int parameterIndex, final Time x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setTime(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setTime(final int parameterIndex, final Time x, final Calendar cal) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setTime(parameterIndex, x, cal); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setTimestamp(final int parameterIndex, final Timestamp x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setTimestamp(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setTimestamp(final int parameterIndex, final Timestamp x, final Calendar cal) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setTimestamp(parameterIndex, x, cal); + } catch (final SQLException e) { + handleException(e); + } + } + + /** @deprecated Use setAsciiStream(), setCharacterStream() or setNCharacterStream() */ + @Deprecated + @Override + public void setUnicodeStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setUnicodeStream(parameterIndex, x, length); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setURL(final int parameterIndex, final java.net.URL x) throws SQLException { + checkOpen(); + try { + getDelegatePreparedStatement().setURL(parameterIndex, x); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Returns a String representation of this object. + * + * @return String + */ + @SuppressWarnings("resource") + @Override + public synchronized String toString() { + return Objects.toString(getDelegate(), "NULL"); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java index 6c30b5555e..df4b2115ff 100644 --- a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java +++ b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java @@ -1,816 +1,816 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * A base delegating implementation of {@link Statement}. - *

- * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and - * call the corresponding method on the "delegate" provided in my constructor. - *

- * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the - * Statement ensures that the Connection which created it can close any open Statement's on Connection close. - * - * @since 2.0 - */ -public class DelegatingStatement extends AbandonedTrace implements Statement { - - /** My delegate. */ - private Statement statement; - - /** The connection that created me. **/ - private DelegatingConnection connection; - - private boolean closed; - - /** - * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code - * which created it. - * - * @param statement - * the {@link Statement} to delegate all calls to. - * @param connection - * the {@link DelegatingConnection} that created this statement. - */ - public DelegatingStatement(final DelegatingConnection connection, final Statement statement) { - super(connection); - this.statement = statement; - this.connection = connection; - } - - /** - * - * @throws SQLException - * thrown by the delegating statement. - * @since 2.4.0 made public, was protected in 2.3.0. - */ - public void activate() throws SQLException { - if (statement instanceof DelegatingStatement) { - ((DelegatingStatement) statement).activate(); - } - } - - @Override - public void addBatch(final String sql) throws SQLException { - checkOpen(); - try { - statement.addBatch(sql); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void cancel() throws SQLException { - checkOpen(); - try { - statement.cancel(); - } catch (final SQLException e) { - handleException(e); - } - } - - protected void checkOpen() throws SQLException { - if (isClosed()) { - throw new SQLException(this.getClass().getName() + " with address: \"" + toString() + "\" is closed."); - } - } - - @Override - public void clearBatch() throws SQLException { - checkOpen(); - try { - statement.clearBatch(); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void clearWarnings() throws SQLException { - checkOpen(); - try { - statement.clearWarnings(); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed. - */ - @Override - public void close() throws SQLException { - if (isClosed()) { - return; - } - final List thrownList = new ArrayList<>(); - try { - if (connection != null) { - connection.removeTrace(this); - connection = null; - } - - // The JDBC spec requires that a statement close any open - // ResultSet's when it is closed. - // FIXME The PreparedStatement we're wrapping should handle this for us. - // See bug 17301 for what could happen when ResultSets are closed twice. - final List traceList = getTrace(); - if (traceList != null) { - traceList.forEach(trace -> trace.close(e -> { - if (connection != null) { - // Does not rethrow e. - connection.handleExceptionNoThrow(e); - } - thrownList.add(e); - })); - clearTrace(); - } - Utils.close(statement, e -> { - if (connection != null) { - // Does not rethrow e. - connection.handleExceptionNoThrow(e); - } - thrownList.add(e); - }); - } finally { - closed = true; - statement = null; - if (!thrownList.isEmpty()) { - throw new SQLExceptionList(thrownList); - } - } - } - - @Override - public void closeOnCompletion() throws SQLException { - checkOpen(); - try { - Jdbc41Bridge.closeOnCompletion(statement); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public boolean execute(final String sql) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.execute(sql); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.execute(sql, autoGeneratedKeys); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean execute(final String sql, final int[] columnIndexes) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.execute(sql, columnIndexes); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean execute(final String sql, final String[] columnNames) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.execute(sql, columnNames); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public int[] executeBatch() throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeBatch(); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - /** - * @since 2.5.0 - */ - @Override - public long[] executeLargeBatch() throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeLargeBatch(); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long executeLargeUpdate(final String sql) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeLargeUpdate(sql); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeLargeUpdate(sql, autoGeneratedKeys); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeLargeUpdate(sql, columnIndexes); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeLargeUpdate(sql, columnNames); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public ResultSet executeQuery(final String sql) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql)); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - @Override - public int executeUpdate(final String sql) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeUpdate(sql); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeUpdate(sql, autoGeneratedKeys); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeUpdate(sql, columnIndexes); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int executeUpdate(final String sql, final String[] columnNames) throws SQLException { - checkOpen(); - setLastUsedInParent(); - try { - return statement.executeUpdate(sql, columnNames); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - protected void finalize() throws Throwable { - // This is required because of statement pooling. The poolable - // statements will always be strongly held by the statement pool. If the - // delegating statements that wrap the poolable statement are not - // strongly held they will be garbage collected but at that point the - // poolable statements need to be returned to the pool else there will - // be a leak of statements from the pool. Closing this statement will - // close all the wrapped statements and return any poolable statements - // to the pool. - close(); - super.finalize(); - } - - @Override - public Connection getConnection() throws SQLException { - checkOpen(); - return getConnectionInternal(); // return the delegating connection that created this - } - - protected DelegatingConnection getConnectionInternal() { - return connection; - } - - /** - * Returns my underlying {@link Statement}. - * - * @return my underlying {@link Statement}. - * @see #getInnermostDelegate - */ - public Statement getDelegate() { - return statement; - } - - @Override - public int getFetchDirection() throws SQLException { - checkOpen(); - try { - return statement.getFetchDirection(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getFetchSize() throws SQLException { - checkOpen(); - try { - return statement.getFetchSize(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public ResultSet getGeneratedKeys() throws SQLException { - checkOpen(); - try { - return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys()); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - /** - * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively - * invokes this method on my delegate. - *

- * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when - * no non-{@code DelegatingStatement} delegate can be found by traversing this chain. - *

- *

- * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain - * a "genuine" {@link Statement}. - *

- * - * @return The innermost delegate, may return null. - * @see #getDelegate - */ - @SuppressWarnings("resource") - public Statement getInnermostDelegate() { - Statement stmt = statement; - while (stmt instanceof DelegatingStatement) { - stmt = ((DelegatingStatement) stmt).getDelegate(); - if (this == stmt) { - return null; - } - } - return stmt; - } - - /** - * @since 2.5.0 - */ - @Override - public long getLargeMaxRows() throws SQLException { - checkOpen(); - try { - return statement.getLargeMaxRows(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - /** - * @since 2.5.0 - */ - @Override - public long getLargeUpdateCount() throws SQLException { - checkOpen(); - try { - return statement.getLargeUpdateCount(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getMaxFieldSize() throws SQLException { - checkOpen(); - try { - return statement.getMaxFieldSize(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getMaxRows() throws SQLException { - checkOpen(); - try { - return statement.getMaxRows(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public boolean getMoreResults() throws SQLException { - checkOpen(); - try { - return statement.getMoreResults(); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean getMoreResults(final int current) throws SQLException { - checkOpen(); - try { - return statement.getMoreResults(current); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public int getQueryTimeout() throws SQLException { - checkOpen(); - try { - return statement.getQueryTimeout(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @SuppressWarnings("resource") // Caller is responsible for closing the resource. - @Override - public ResultSet getResultSet() throws SQLException { - checkOpen(); - try { - return DelegatingResultSet.wrapResultSet(this, statement.getResultSet()); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - @Override - public int getResultSetConcurrency() throws SQLException { - checkOpen(); - try { - return statement.getResultSetConcurrency(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getResultSetHoldability() throws SQLException { - checkOpen(); - try { - return statement.getResultSetHoldability(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getResultSetType() throws SQLException { - checkOpen(); - try { - return statement.getResultSetType(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public int getUpdateCount() throws SQLException { - checkOpen(); - try { - return statement.getUpdateCount(); - } catch (final SQLException e) { - handleException(e); - return 0; - } - } - - @Override - public SQLWarning getWarnings() throws SQLException { - checkOpen(); - try { - return statement.getWarnings(); - } catch (final SQLException e) { - handleException(e); - throw new AssertionError(); - } - } - - protected void handleException(final SQLException e) throws SQLException { - if (connection == null) { - throw e; - } - connection.handleException(e); - } - - /* - * Note: This method was protected prior to JDBC 4. - */ - @Override - public boolean isClosed() throws SQLException { - return closed; - } - - protected boolean isClosedInternal() { - return closed; - } - - @Override - public boolean isCloseOnCompletion() throws SQLException { - checkOpen(); - try { - return Jdbc41Bridge.isCloseOnCompletion(statement); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean isPoolable() throws SQLException { - checkOpen(); - try { - return statement.isPoolable(); - } catch (final SQLException e) { - handleException(e); - return false; - } - } - - @Override - public boolean isWrapperFor(final Class iface) throws SQLException { - if (iface.isAssignableFrom(getClass())) { - return true; - } - if (iface.isAssignableFrom(statement.getClass())) { - return true; - } - return statement.isWrapperFor(iface); - } - - /** - * - * @throws SQLException - * thrown by the delegating statement. - * @since 2.4.0 made public, was protected in 2.3.0. - */ - public void passivate() throws SQLException { - if (statement instanceof DelegatingStatement) { - ((DelegatingStatement) statement).passivate(); - } - } - - protected void setClosedInternal(final boolean closed) { - this.closed = closed; - } - - @Override - public void setCursorName(final String name) throws SQLException { - checkOpen(); - try { - statement.setCursorName(name); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Sets my delegate. - * - * @param statement - * my delegate. - */ - public void setDelegate(final Statement statement) { - this.statement = statement; - } - - @Override - public void setEscapeProcessing(final boolean enable) throws SQLException { - checkOpen(); - try { - statement.setEscapeProcessing(enable); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setFetchDirection(final int direction) throws SQLException { - checkOpen(); - try { - statement.setFetchDirection(direction); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setFetchSize(final int rows) throws SQLException { - checkOpen(); - try { - statement.setFetchSize(rows); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * @since 2.5.0 - */ - @Override - public void setLargeMaxRows(final long max) throws SQLException { - checkOpen(); - try { - statement.setLargeMaxRows(max); - } catch (final SQLException e) { - handleException(e); - } - } - - private void setLastUsedInParent() { - if (connection != null) { - connection.setLastUsed(); - } - } - - @Override - public void setMaxFieldSize(final int max) throws SQLException { - checkOpen(); - try { - statement.setMaxFieldSize(max); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setMaxRows(final int max) throws SQLException { - checkOpen(); - try { - statement.setMaxRows(max); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setPoolable(final boolean poolable) throws SQLException { - checkOpen(); - try { - statement.setPoolable(poolable); - } catch (final SQLException e) { - handleException(e); - } - } - - @Override - public void setQueryTimeout(final int seconds) throws SQLException { - checkOpen(); - try { - statement.setQueryTimeout(seconds); - } catch (final SQLException e) { - handleException(e); - } - } - - /** - * Returns a String representation of this object. - * - * @return String - */ - @Override - public synchronized String toString() { - return Objects.toString(statement, "NULL"); - } - - @Override - public T unwrap(final Class iface) throws SQLException { - if (iface.isAssignableFrom(getClass())) { - return iface.cast(this); - } - if (iface.isAssignableFrom(statement.getClass())) { - return iface.cast(statement); - } - return statement.unwrap(iface); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A base delegating implementation of {@link Statement}. + *

+ * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and + * call the corresponding method on the "delegate" provided in my constructor. + *

+ * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the + * Statement ensures that the Connection which created it can close any open Statement's on Connection close. + * + * @since 2.0 + */ +public class DelegatingStatement extends AbandonedTrace implements Statement { + + /** My delegate. */ + private Statement statement; + + /** The connection that created me. **/ + private DelegatingConnection connection; + + private boolean closed; + + /** + * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code + * which created it. + * + * @param statement + * the {@link Statement} to delegate all calls to. + * @param connection + * the {@link DelegatingConnection} that created this statement. + */ + public DelegatingStatement(final DelegatingConnection connection, final Statement statement) { + super(connection); + this.statement = statement; + this.connection = connection; + } + + /** + * + * @throws SQLException + * thrown by the delegating statement. + * @since 2.4.0 made public, was protected in 2.3.0. + */ + public void activate() throws SQLException { + if (statement instanceof DelegatingStatement) { + ((DelegatingStatement) statement).activate(); + } + } + + @Override + public void addBatch(final String sql) throws SQLException { + checkOpen(); + try { + statement.addBatch(sql); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void cancel() throws SQLException { + checkOpen(); + try { + statement.cancel(); + } catch (final SQLException e) { + handleException(e); + } + } + + protected void checkOpen() throws SQLException { + if (isClosed()) { + throw new SQLException(this.getClass().getName() + " with address: \"" + toString() + "\" is closed."); + } + } + + @Override + public void clearBatch() throws SQLException { + checkOpen(); + try { + statement.clearBatch(); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void clearWarnings() throws SQLException { + checkOpen(); + try { + statement.clearWarnings(); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed. + */ + @Override + public void close() throws SQLException { + if (isClosed()) { + return; + } + final List thrownList = new ArrayList<>(); + try { + if (connection != null) { + connection.removeTrace(this); + connection = null; + } + + // The JDBC spec requires that a statement close any open + // ResultSet's when it is closed. + // FIXME The PreparedStatement we're wrapping should handle this for us. + // See bug 17301 for what could happen when ResultSets are closed twice. + final List traceList = getTrace(); + if (traceList != null) { + traceList.forEach(trace -> trace.close(e -> { + if (connection != null) { + // Does not rethrow e. + connection.handleExceptionNoThrow(e); + } + thrownList.add(e); + })); + clearTrace(); + } + Utils.close(statement, e -> { + if (connection != null) { + // Does not rethrow e. + connection.handleExceptionNoThrow(e); + } + thrownList.add(e); + }); + } finally { + closed = true; + statement = null; + if (!thrownList.isEmpty()) { + throw new SQLExceptionList(thrownList); + } + } + } + + @Override + public void closeOnCompletion() throws SQLException { + checkOpen(); + try { + Jdbc41Bridge.closeOnCompletion(statement); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public boolean execute(final String sql) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.execute(sql); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.execute(sql, autoGeneratedKeys); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean execute(final String sql, final int[] columnIndexes) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.execute(sql, columnIndexes); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean execute(final String sql, final String[] columnNames) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.execute(sql, columnNames); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public int[] executeBatch() throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeBatch(); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + /** + * @since 2.5.0 + */ + @Override + public long[] executeLargeBatch() throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeLargeBatch(); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long executeLargeUpdate(final String sql) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeLargeUpdate(sql); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeLargeUpdate(sql, autoGeneratedKeys); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeLargeUpdate(sql, columnIndexes); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeLargeUpdate(sql, columnNames); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public ResultSet executeQuery(final String sql) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql)); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + @Override + public int executeUpdate(final String sql) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeUpdate(sql); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeUpdate(sql, autoGeneratedKeys); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeUpdate(sql, columnIndexes); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int executeUpdate(final String sql, final String[] columnNames) throws SQLException { + checkOpen(); + setLastUsedInParent(); + try { + return statement.executeUpdate(sql, columnNames); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + protected void finalize() throws Throwable { + // This is required because of statement pooling. The poolable + // statements will always be strongly held by the statement pool. If the + // delegating statements that wrap the poolable statement are not + // strongly held they will be garbage collected but at that point the + // poolable statements need to be returned to the pool else there will + // be a leak of statements from the pool. Closing this statement will + // close all the wrapped statements and return any poolable statements + // to the pool. + close(); + super.finalize(); + } + + @Override + public Connection getConnection() throws SQLException { + checkOpen(); + return getConnectionInternal(); // return the delegating connection that created this + } + + protected DelegatingConnection getConnectionInternal() { + return connection; + } + + /** + * Returns my underlying {@link Statement}. + * + * @return my underlying {@link Statement}. + * @see #getInnermostDelegate + */ + public Statement getDelegate() { + return statement; + } + + @Override + public int getFetchDirection() throws SQLException { + checkOpen(); + try { + return statement.getFetchDirection(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getFetchSize() throws SQLException { + checkOpen(); + try { + return statement.getFetchSize(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public ResultSet getGeneratedKeys() throws SQLException { + checkOpen(); + try { + return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys()); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + /** + * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively + * invokes this method on my delegate. + *

+ * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when + * no non-{@code DelegatingStatement} delegate can be found by traversing this chain. + *

+ *

+ * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain + * a "genuine" {@link Statement}. + *

+ * + * @return The innermost delegate, may return null. + * @see #getDelegate + */ + @SuppressWarnings("resource") + public Statement getInnermostDelegate() { + Statement stmt = statement; + while (stmt instanceof DelegatingStatement) { + stmt = ((DelegatingStatement) stmt).getDelegate(); + if (this == stmt) { + return null; + } + } + return stmt; + } + + /** + * @since 2.5.0 + */ + @Override + public long getLargeMaxRows() throws SQLException { + checkOpen(); + try { + return statement.getLargeMaxRows(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + /** + * @since 2.5.0 + */ + @Override + public long getLargeUpdateCount() throws SQLException { + checkOpen(); + try { + return statement.getLargeUpdateCount(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getMaxFieldSize() throws SQLException { + checkOpen(); + try { + return statement.getMaxFieldSize(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getMaxRows() throws SQLException { + checkOpen(); + try { + return statement.getMaxRows(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public boolean getMoreResults() throws SQLException { + checkOpen(); + try { + return statement.getMoreResults(); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean getMoreResults(final int current) throws SQLException { + checkOpen(); + try { + return statement.getMoreResults(current); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public int getQueryTimeout() throws SQLException { + checkOpen(); + try { + return statement.getQueryTimeout(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @SuppressWarnings("resource") // Caller is responsible for closing the resource. + @Override + public ResultSet getResultSet() throws SQLException { + checkOpen(); + try { + return DelegatingResultSet.wrapResultSet(this, statement.getResultSet()); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + @Override + public int getResultSetConcurrency() throws SQLException { + checkOpen(); + try { + return statement.getResultSetConcurrency(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getResultSetHoldability() throws SQLException { + checkOpen(); + try { + return statement.getResultSetHoldability(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getResultSetType() throws SQLException { + checkOpen(); + try { + return statement.getResultSetType(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public int getUpdateCount() throws SQLException { + checkOpen(); + try { + return statement.getUpdateCount(); + } catch (final SQLException e) { + handleException(e); + return 0; + } + } + + @Override + public SQLWarning getWarnings() throws SQLException { + checkOpen(); + try { + return statement.getWarnings(); + } catch (final SQLException e) { + handleException(e); + throw new AssertionError(); + } + } + + protected void handleException(final SQLException e) throws SQLException { + if (connection == null) { + throw e; + } + connection.handleException(e); + } + + /* + * Note: This method was protected prior to JDBC 4. + */ + @Override + public boolean isClosed() throws SQLException { + return closed; + } + + protected boolean isClosedInternal() { + return closed; + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + checkOpen(); + try { + return Jdbc41Bridge.isCloseOnCompletion(statement); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean isPoolable() throws SQLException { + checkOpen(); + try { + return statement.isPoolable(); + } catch (final SQLException e) { + handleException(e); + return false; + } + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + if (iface.isAssignableFrom(getClass())) { + return true; + } + if (iface.isAssignableFrom(statement.getClass())) { + return true; + } + return statement.isWrapperFor(iface); + } + + /** + * + * @throws SQLException + * thrown by the delegating statement. + * @since 2.4.0 made public, was protected in 2.3.0. + */ + public void passivate() throws SQLException { + if (statement instanceof DelegatingStatement) { + ((DelegatingStatement) statement).passivate(); + } + } + + protected void setClosedInternal(final boolean closed) { + this.closed = closed; + } + + @Override + public void setCursorName(final String name) throws SQLException { + checkOpen(); + try { + statement.setCursorName(name); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Sets my delegate. + * + * @param statement + * my delegate. + */ + public void setDelegate(final Statement statement) { + this.statement = statement; + } + + @Override + public void setEscapeProcessing(final boolean enable) throws SQLException { + checkOpen(); + try { + statement.setEscapeProcessing(enable); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setFetchDirection(final int direction) throws SQLException { + checkOpen(); + try { + statement.setFetchDirection(direction); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setFetchSize(final int rows) throws SQLException { + checkOpen(); + try { + statement.setFetchSize(rows); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * @since 2.5.0 + */ + @Override + public void setLargeMaxRows(final long max) throws SQLException { + checkOpen(); + try { + statement.setLargeMaxRows(max); + } catch (final SQLException e) { + handleException(e); + } + } + + private void setLastUsedInParent() { + if (connection != null) { + connection.setLastUsed(); + } + } + + @Override + public void setMaxFieldSize(final int max) throws SQLException { + checkOpen(); + try { + statement.setMaxFieldSize(max); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setMaxRows(final int max) throws SQLException { + checkOpen(); + try { + statement.setMaxRows(max); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setPoolable(final boolean poolable) throws SQLException { + checkOpen(); + try { + statement.setPoolable(poolable); + } catch (final SQLException e) { + handleException(e); + } + } + + @Override + public void setQueryTimeout(final int seconds) throws SQLException { + checkOpen(); + try { + statement.setQueryTimeout(seconds); + } catch (final SQLException e) { + handleException(e); + } + } + + /** + * Returns a String representation of this object. + * + * @return String + */ + @Override + public synchronized String toString() { + return Objects.toString(statement, "NULL"); + } + + @Override + public T unwrap(final Class iface) throws SQLException { + if (iface.isAssignableFrom(getClass())) { + return iface.cast(this); + } + if (iface.isAssignableFrom(statement.getClass())) { + return iface.cast(statement); + } + return statement.unwrap(iface); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/DriverFactory.java b/src/main/java/org/apache/commons/dbcp2/DriverFactory.java index 77704e48c8..80b1294579 100644 --- a/src/main/java/org/apache/commons/dbcp2/DriverFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/DriverFactory.java @@ -1,80 +1,80 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; - -/* - * Creates {@link Driver} instances. - * - * @since 2.7.0 - */ -final class DriverFactory { - - static Driver createDriver(final BasicDataSource basicDataSource) throws SQLException { - // Load the JDBC driver class - Driver driverToUse = basicDataSource.getDriver(); - final String driverClassName = basicDataSource.getDriverClassName(); - final ClassLoader driverClassLoader = basicDataSource.getDriverClassLoader(); - final String url = basicDataSource.getUrl(); - - if (driverToUse == null) { - Class driverFromCCL = null; - if (driverClassName != null) { - try { - try { - if (driverClassLoader == null) { - driverFromCCL = Class.forName(driverClassName); - } else { - driverFromCCL = Class.forName(driverClassName, true, driverClassLoader); - } - } catch (final ClassNotFoundException cnfe) { - driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName); - } - } catch (final Exception t) { - final String message = "Cannot load JDBC driver class '" + driverClassName + "'"; - basicDataSource.log(message, t); - throw new SQLException(message, t); - } - } - - try { - if (driverFromCCL == null) { - driverToUse = DriverManager.getDriver(url); - } else { - // Usage of DriverManager is not possible, as it does not - // respect the ContextClassLoader - // N.B. This cast may cause ClassCastException which is - // handled below - driverToUse = (Driver) driverFromCCL.getConstructor().newInstance(); - if (!driverToUse.acceptsURL(url)) { - throw new SQLException("No suitable driver", "08001"); - } - } - } catch (final Exception t) { - final String message = "Cannot create JDBC driver of class '" - + (driverClassName != null ? driverClassName : "") + "' for connect URL '" + url + "'"; - basicDataSource.log(message, t); - throw new SQLException(message, t); - } - } - return driverToUse; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; + +/* + * Creates {@link Driver} instances. + * + * @since 2.7.0 + */ +final class DriverFactory { + + static Driver createDriver(final BasicDataSource basicDataSource) throws SQLException { + // Load the JDBC driver class + Driver driverToUse = basicDataSource.getDriver(); + final String driverClassName = basicDataSource.getDriverClassName(); + final ClassLoader driverClassLoader = basicDataSource.getDriverClassLoader(); + final String url = basicDataSource.getUrl(); + + if (driverToUse == null) { + Class driverFromCCL = null; + if (driverClassName != null) { + try { + try { + if (driverClassLoader == null) { + driverFromCCL = Class.forName(driverClassName); + } else { + driverFromCCL = Class.forName(driverClassName, true, driverClassLoader); + } + } catch (final ClassNotFoundException cnfe) { + driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName); + } + } catch (final Exception t) { + final String message = "Cannot load JDBC driver class '" + driverClassName + "'"; + basicDataSource.log(message, t); + throw new SQLException(message, t); + } + } + + try { + if (driverFromCCL == null) { + driverToUse = DriverManager.getDriver(url); + } else { + // Usage of DriverManager is not possible, as it does not + // respect the ContextClassLoader + // N.B. This cast may cause ClassCastException which is + // handled below + driverToUse = (Driver) driverFromCCL.getConstructor().newInstance(); + if (!driverToUse.acceptsURL(url)) { + throw new SQLException("No suitable driver", "08001"); + } + } + } catch (final Exception t) { + final String message = "Cannot create JDBC driver of class '" + + (driverClassName != null ? driverClassName : "") + "' for connect URL '" + url + "'"; + basicDataSource.log(message, t); + throw new SQLException(message, t); + } + } + return driverToUse; + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/DriverManagerConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/DriverManagerConnectionFactory.java index 4564442a58..bad3b7ded5 100644 --- a/src/main/java/org/apache/commons/dbcp2/DriverManagerConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/DriverManagerConnectionFactory.java @@ -1,148 +1,148 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Properties; - -/** - * A {@link DriverManager}-based implementation of {@link ConnectionFactory}. - * - * @since 2.0 - */ -public class DriverManagerConnectionFactory implements ConnectionFactory { - - static { - // Related to DBCP-212 - // Driver manager does not sync loading of drivers that use the service - // provider interface. This will cause issues is multi-threaded - // environments. This hack makes sure the drivers are loaded before - // DBCP tries to use them. - DriverManager.getDrivers(); - } - - private final String connectionUri; - - private final String userName; - - private final char[] userPassword; - - private final Properties properties; - - /** - * Constructor for DriverManagerConnectionFactory. - * - * @param connectionUri - * a database connection string of the form {@code jdbc:subprotocol:subname} - * @since 2.2 - */ - public DriverManagerConnectionFactory(final String connectionUri) { - this.connectionUri = connectionUri; - this.properties = new Properties(); - this.userName = null; - this.userPassword = null; - } - - /** - * Constructor for DriverManagerConnectionFactory. - * - * @param connectionUri - * a database connection string of the form {@code jdbc:subprotocol:subname} - * @param properties - * a list of arbitrary string tag/value pairs as connection arguments; normally at least a "user" and - * "password" property should be included. - */ - public DriverManagerConnectionFactory(final String connectionUri, final Properties properties) { - this.connectionUri = connectionUri; - this.properties = properties; - this.userName = null; - this.userPassword = null; - } - - /** - * Constructor for DriverManagerConnectionFactory. - * - * @param connectionUri - * a database connection string of the form {@code jdbc:subprotocol:subname} - * @param userName - * the database user - * @param userPassword - * the user's password - */ - public DriverManagerConnectionFactory(final String connectionUri, final String userName, - final char[] userPassword) { - this.connectionUri = connectionUri; - this.userName = userName; - this.userPassword = Utils.clone(userPassword); - this.properties = null; - } - - /** - * Constructor for DriverManagerConnectionFactory. - * - * @param connectionUri - * a database connection string of the form {@code jdbc:subprotocol:subname} - * @param userName - * the database user - * @param userPassword - * the user's password - */ - public DriverManagerConnectionFactory(final String connectionUri, final String userName, - final String userPassword) { - this.connectionUri = connectionUri; - this.userName = userName; - this.userPassword = Utils.toCharArray(userPassword); - this.properties = null; - } - - @Override - public Connection createConnection() throws SQLException { - if (null == properties) { - if (userName == null && userPassword == null) { - return DriverManager.getConnection(connectionUri); - } - return DriverManager.getConnection(connectionUri, userName, Utils.toString(userPassword)); - } - return DriverManager.getConnection(connectionUri, properties); - } - - /** - * @return The connection URI. - * @since 2.6.0 - */ - public String getConnectionUri() { - return connectionUri; - } - - /** - * @return The Properties. - * @since 2.6.0 - */ - public Properties getProperties() { - return properties; - } - - /** - * @return The user name. - * @since 2.6.0 - */ - public String getUserName() { - return userName; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +/** + * A {@link DriverManager}-based implementation of {@link ConnectionFactory}. + * + * @since 2.0 + */ +public class DriverManagerConnectionFactory implements ConnectionFactory { + + static { + // Related to DBCP-212 + // Driver manager does not sync loading of drivers that use the service + // provider interface. This will cause issues is multi-threaded + // environments. This hack makes sure the drivers are loaded before + // DBCP tries to use them. + DriverManager.getDrivers(); + } + + private final String connectionUri; + + private final String userName; + + private final char[] userPassword; + + private final Properties properties; + + /** + * Constructor for DriverManagerConnectionFactory. + * + * @param connectionUri + * a database connection string of the form {@code jdbc:subprotocol:subname} + * @since 2.2 + */ + public DriverManagerConnectionFactory(final String connectionUri) { + this.connectionUri = connectionUri; + this.properties = new Properties(); + this.userName = null; + this.userPassword = null; + } + + /** + * Constructor for DriverManagerConnectionFactory. + * + * @param connectionUri + * a database connection string of the form {@code jdbc:subprotocol:subname} + * @param properties + * a list of arbitrary string tag/value pairs as connection arguments; normally at least a "user" and + * "password" property should be included. + */ + public DriverManagerConnectionFactory(final String connectionUri, final Properties properties) { + this.connectionUri = connectionUri; + this.properties = properties; + this.userName = null; + this.userPassword = null; + } + + /** + * Constructor for DriverManagerConnectionFactory. + * + * @param connectionUri + * a database connection string of the form {@code jdbc:subprotocol:subname} + * @param userName + * the database user + * @param userPassword + * the user's password + */ + public DriverManagerConnectionFactory(final String connectionUri, final String userName, + final char[] userPassword) { + this.connectionUri = connectionUri; + this.userName = userName; + this.userPassword = Utils.clone(userPassword); + this.properties = null; + } + + /** + * Constructor for DriverManagerConnectionFactory. + * + * @param connectionUri + * a database connection string of the form {@code jdbc:subprotocol:subname} + * @param userName + * the database user + * @param userPassword + * the user's password + */ + public DriverManagerConnectionFactory(final String connectionUri, final String userName, + final String userPassword) { + this.connectionUri = connectionUri; + this.userName = userName; + this.userPassword = Utils.toCharArray(userPassword); + this.properties = null; + } + + @Override + public Connection createConnection() throws SQLException { + if (null == properties) { + if (userName == null && userPassword == null) { + return DriverManager.getConnection(connectionUri); + } + return DriverManager.getConnection(connectionUri, userName, Utils.toString(userPassword)); + } + return DriverManager.getConnection(connectionUri, properties); + } + + /** + * @return The connection URI. + * @since 2.6.0 + */ + public String getConnectionUri() { + return connectionUri; + } + + /** + * @return The Properties. + * @since 2.6.0 + */ + public Properties getProperties() { + return properties; + } + + /** + * @return The user name. + * @since 2.6.0 + */ + public String getUserName() { + return userName; + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java b/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java index fbd6ae3c63..e9f3146641 100644 --- a/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java +++ b/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java @@ -1,488 +1,488 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.net.URL; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.Date; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.RowId; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.SQLXML; -import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.concurrent.Executor; -import java.util.logging.Logger; - -import javax.sql.CommonDataSource; - -/** - * Defines bridge methods to JDBC 4.1 (Java 7 or above) methods to allow call sites to operate safely (without - * {@link AbstractMethodError}) when using a JDBC driver written for JDBC 4.0 (Java 6 or above). - *

- * There should be no need to this kind of code for JDBC 4.2 in Java 8 due to JDBC's use of default methods. - *

- *

- * This should probably be moved or at least copied in some form to Apache Commons DbUtils. - *

- * - * @since 2.6.0 - */ -public class Jdbc41Bridge { - - /** - * Delegates to {@link Connection#abort(Executor)} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Connection#abort(Executor)}, then call {@link Connection#close()}. - *

- * - * @param connection - * the receiver - * @param executor - * See {@link Connection#abort(Executor)}. - * @throws SQLException - * See {@link Connection#abort(Executor)}. - * @see Connection#abort(Executor) - */ - public static void abort(final Connection connection, final Executor executor) throws SQLException { - try { - connection.abort(executor); - } catch (final AbstractMethodError e) { - connection.close(); - } - } - - /** - * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection - * is closed to then throw an SQLException. - *

- * - * @param statement - * See {@link Statement#closeOnCompletion()} - * @throws SQLException - * See {@link Statement#closeOnCompletion()} - * @see Statement#closeOnCompletion() - */ - public static void closeOnCompletion(final Statement statement) throws SQLException { - try { - statement.closeOnCompletion(); - } catch (final AbstractMethodError e) { - if (statement.isClosed()) { - throw new SQLException("Statement closed"); - } - } - } - - /** - * Delegates to {@link DatabaseMetaData#generatedKeyAlwaysReturned()} without throwing a - * {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link DatabaseMetaData#generatedKeyAlwaysReturned()}, then return false. - *

- * - * @param databaseMetaData - * See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} - * @return See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} - * @throws SQLException - * See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} - * @see DatabaseMetaData#generatedKeyAlwaysReturned() - */ - public static boolean generatedKeyAlwaysReturned(final DatabaseMetaData databaseMetaData) throws SQLException { - try { - return databaseMetaData.generatedKeyAlwaysReturned(); - } catch (final AbstractMethodError e) { - // do nothing - return false; - } - } - - /** - * Delegates to {@link Connection#getNetworkTimeout()} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Connection#getNetworkTimeout()}, then return 0. - *

- * - * @param connection - * the receiver - * @return See {@link Connection#getNetworkTimeout()} - * @throws SQLException - * See {@link Connection#getNetworkTimeout()} - * @see Connection#getNetworkTimeout() - */ - public static int getNetworkTimeout(final Connection connection) throws SQLException { - try { - return connection.getNetworkTimeout(); - } catch (final AbstractMethodError e) { - return 0; - } - } - - /** - * Delegates to {@link ResultSet#getObject(int, Class)} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link ResultSet#getObject(int, Class)}, then return 0. - *

- * - * @param - * See {@link ResultSet#getObject(int, Class)} - * @param resultSet - * See {@link ResultSet#getObject(int, Class)} - * @param columnIndex - * See {@link ResultSet#getObject(int, Class)} - * @param type - * See {@link ResultSet#getObject(int, Class)} - * @return See {@link ResultSet#getObject(int, Class)} - * @throws SQLException - * See {@link ResultSet#getObject(int, Class)} - * @see ResultSet#getObject(int, Class) - */ - @SuppressWarnings("unchecked") - public static T getObject(final ResultSet resultSet, final int columnIndex, final Class type) - throws SQLException { - try { - return resultSet.getObject(columnIndex, type); - } catch (final AbstractMethodError e) { - if (type == String.class) { - return (T) resultSet.getString(columnIndex); - } - // Numbers - if (type == Integer.class) { - return (T) Integer.valueOf(resultSet.getInt(columnIndex)); - } - if (type == Long.class) { - return (T) Long.valueOf(resultSet.getLong(columnIndex)); - } - if (type == Double.class) { - return (T) Double.valueOf(resultSet.getDouble(columnIndex)); - } - if (type == Float.class) { - return (T) Float.valueOf(resultSet.getFloat(columnIndex)); - } - if (type == Short.class) { - return (T) Short.valueOf(resultSet.getShort(columnIndex)); - } - if (type == BigDecimal.class) { - return (T) resultSet.getBigDecimal(columnIndex); - } - if (type == Byte.class) { - return (T) Byte.valueOf(resultSet.getByte(columnIndex)); - } - // Dates - if (type == Date.class) { - return (T) resultSet.getDate(columnIndex); - } - if (type == Time.class) { - return (T) resultSet.getTime(columnIndex); - } - if (type == Timestamp.class) { - return (T) resultSet.getTimestamp(columnIndex); - } - // Streams - if (type == InputStream.class) { - return (T) resultSet.getBinaryStream(columnIndex); - } - if (type == Reader.class) { - return (T) resultSet.getCharacterStream(columnIndex); - } - // Other - if (type == Object.class) { - return (T) resultSet.getObject(columnIndex); - } - if (type == Boolean.class) { - return (T) Boolean.valueOf(resultSet.getBoolean(columnIndex)); - } - if (type == Array.class) { - return (T) resultSet.getArray(columnIndex); - } - if (type == Blob.class) { - return (T) resultSet.getBlob(columnIndex); - } - if (type == Clob.class) { - return (T) resultSet.getClob(columnIndex); - } - if (type == Ref.class) { - return (T) resultSet.getRef(columnIndex); - } - if (type == RowId.class) { - return (T) resultSet.getRowId(columnIndex); - } - if (type == SQLXML.class) { - return (T) resultSet.getSQLXML(columnIndex); - } - if (type == URL.class) { - return (T) resultSet.getURL(columnIndex); - } - throw new SQLFeatureNotSupportedException( - String.format("resultSet=%s, columnIndex=%,d, type=%s", resultSet, columnIndex, type)); - } - } - - /** - * Delegates to {@link ResultSet#getObject(String, Class)} without throwing an {@link AbstractMethodError}. - * - * @param - * See {@link ResultSet#getObject(String, Class)} - * @param resultSet - * See {@link ResultSet#getObject(String, Class)} - * @param columnLabel - * See {@link ResultSet#getObject(String, Class)} - * @param type - * See {@link ResultSet#getObject(String, Class)} - * @return See {@link ResultSet#getObject(String, Class)} - * @throws SQLException - * See {@link ResultSet#getObject(String, Class)} - * @see ResultSet#getObject(int, Class) - */ - @SuppressWarnings("unchecked") - public static T getObject(final ResultSet resultSet, final String columnLabel, final Class type) - throws SQLException { - try { - return resultSet.getObject(columnLabel, type); - } catch (final AbstractMethodError e) { - // Numbers - if (type == Integer.class) { - return (T) Integer.valueOf(resultSet.getInt(columnLabel)); - } - if (type == Long.class) { - return (T) Long.valueOf(resultSet.getLong(columnLabel)); - } - if (type == Double.class) { - return (T) Double.valueOf(resultSet.getDouble(columnLabel)); - } - if (type == Float.class) { - return (T) Float.valueOf(resultSet.getFloat(columnLabel)); - } - if (type == Short.class) { - return (T) Short.valueOf(resultSet.getShort(columnLabel)); - } - if (type == BigDecimal.class) { - return (T) resultSet.getBigDecimal(columnLabel); - } - if (type == Byte.class) { - return (T) Byte.valueOf(resultSet.getByte(columnLabel)); - } - // Dates - if (type == Date.class) { - return (T) resultSet.getDate(columnLabel); - } - if (type == Time.class) { - return (T) resultSet.getTime(columnLabel); - } - if (type == Timestamp.class) { - return (T) resultSet.getTimestamp(columnLabel); - } - // Streams - if (type == InputStream.class) { - return (T) resultSet.getBinaryStream(columnLabel); - } - if (type == Reader.class) { - return (T) resultSet.getCharacterStream(columnLabel); - } - // Other - if (type == Object.class) { - return (T) resultSet.getObject(columnLabel); - } - if (type == Boolean.class) { - return (T) Boolean.valueOf(resultSet.getBoolean(columnLabel)); - } - if (type == Array.class) { - return (T) resultSet.getArray(columnLabel); - } - if (type == Blob.class) { - return (T) resultSet.getBlob(columnLabel); - } - if (type == Clob.class) { - return (T) resultSet.getClob(columnLabel); - } - if (type == Ref.class) { - return (T) resultSet.getRef(columnLabel); - } - if (type == RowId.class) { - return (T) resultSet.getRowId(columnLabel); - } - if (type == SQLXML.class) { - return (T) resultSet.getSQLXML(columnLabel); - } - if (type == URL.class) { - return (T) resultSet.getURL(columnLabel); - } - throw new SQLFeatureNotSupportedException( - String.format("resultSet=%s, columnLabel=%s, type=%s", resultSet, columnLabel, type)); - } - } - - /** - * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null. - *

- * - * @param commonDataSource - * See {@link CommonDataSource#getParentLogger()} - * @return See {@link CommonDataSource#getParentLogger()} - * @throws SQLFeatureNotSupportedException - * See {@link CommonDataSource#getParentLogger()} - */ - public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException { - try { - return commonDataSource.getParentLogger(); - } catch (final AbstractMethodError e) { - throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()"); - } - } - - /** - * Delegates to {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} without throwing a - * {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}, - * then return null. - *

- * - * @param databaseMetaData - * the receiver - * @param catalog - * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @param schemaPattern - * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @param tableNamePattern - * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @param columnNamePattern - * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @return See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @throws SQLException - * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} - * @see DatabaseMetaData#getPseudoColumns(String, String, String, String) - */ - public static ResultSet getPseudoColumns(final DatabaseMetaData databaseMetaData, final String catalog, - final String schemaPattern, final String tableNamePattern, final String columnNamePattern) - throws SQLException { - try { - return databaseMetaData.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); - } catch (final AbstractMethodError e) { - // do nothing - return null; - } - } - - /** - * Delegates to {@link Connection#getSchema()} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Connection#getSchema()}, then return null. - *

- * - * @param connection - * the receiver - * @return null for a JDBC 4 driver or a value per {@link Connection#getSchema()}. - * @throws SQLException - * See {@link Connection#getSchema()}. - * @see Connection#getSchema() - */ - public static String getSchema(final Connection connection) throws SQLException { - try { - return connection.getSchema(); - } catch (final AbstractMethodError e) { - // do nothing - return null; - } - } - - /** - * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the - * connection is closed to then throw an SQLException. - *

- * - * @param statement - * See {@link Statement#isCloseOnCompletion()} - * @return See {@link Statement#isCloseOnCompletion()} - * @throws SQLException - * See {@link Statement#isCloseOnCompletion()} - * @see Statement#closeOnCompletion() - */ - public static boolean isCloseOnCompletion(final Statement statement) throws SQLException { - try { - return statement.isCloseOnCompletion(); - } catch (final AbstractMethodError e) { - if (statement.isClosed()) { - throw new SQLException("Statement closed"); - } - return false; - } - } - - /** - * Delegates to {@link Connection#setNetworkTimeout(Executor, int)} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Connection#setNetworkTimeout(Executor, int)}, then do nothing. - *

- * - * @param connection - * the receiver - * @param executor - * See {@link Connection#setNetworkTimeout(Executor, int)} - * @param milliseconds - * {@link Connection#setNetworkTimeout(Executor, int)} - * @throws SQLException - * {@link Connection#setNetworkTimeout(Executor, int)} - * @see Connection#setNetworkTimeout(Executor, int) - */ - public static void setNetworkTimeout(final Connection connection, final Executor executor, final int milliseconds) - throws SQLException { - try { - connection.setNetworkTimeout(executor, milliseconds); - } catch (final AbstractMethodError ignored) { - // do nothing - } - } - - /** - * Delegates to {@link Connection#setSchema(String)} without throwing an {@link AbstractMethodError}. - *

- * If the JDBC driver does not implement {@link Connection#setSchema(String)}, then do nothing. - *

- * - * @param connection - * the receiver - * @param schema - * See {@link Connection#setSchema(String)}. - * @throws SQLException - * See {@link Connection#setSchema(String)}. - * @see Connection#setSchema(String) - */ - public static void setSchema(final Connection connection, final String schema) throws SQLException { - try { - connection.setSchema(schema); - } catch (final AbstractMethodError ignored) { - // do nothing - } - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.sql.CommonDataSource; + +/** + * Defines bridge methods to JDBC 4.1 (Java 7 or above) methods to allow call sites to operate safely (without + * {@link AbstractMethodError}) when using a JDBC driver written for JDBC 4.0 (Java 6 or above). + *

+ * There should be no need to this kind of code for JDBC 4.2 in Java 8 due to JDBC's use of default methods. + *

+ *

+ * This should probably be moved or at least copied in some form to Apache Commons DbUtils. + *

+ * + * @since 2.6.0 + */ +public class Jdbc41Bridge { + + /** + * Delegates to {@link Connection#abort(Executor)} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Connection#abort(Executor)}, then call {@link Connection#close()}. + *

+ * + * @param connection + * the receiver + * @param executor + * See {@link Connection#abort(Executor)}. + * @throws SQLException + * See {@link Connection#abort(Executor)}. + * @see Connection#abort(Executor) + */ + public static void abort(final Connection connection, final Executor executor) throws SQLException { + try { + connection.abort(executor); + } catch (final AbstractMethodError e) { + connection.close(); + } + } + + /** + * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection + * is closed to then throw an SQLException. + *

+ * + * @param statement + * See {@link Statement#closeOnCompletion()} + * @throws SQLException + * See {@link Statement#closeOnCompletion()} + * @see Statement#closeOnCompletion() + */ + public static void closeOnCompletion(final Statement statement) throws SQLException { + try { + statement.closeOnCompletion(); + } catch (final AbstractMethodError e) { + if (statement.isClosed()) { + throw new SQLException("Statement closed"); + } + } + } + + /** + * Delegates to {@link DatabaseMetaData#generatedKeyAlwaysReturned()} without throwing a + * {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link DatabaseMetaData#generatedKeyAlwaysReturned()}, then return false. + *

+ * + * @param databaseMetaData + * See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} + * @return See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} + * @throws SQLException + * See {@link DatabaseMetaData#generatedKeyAlwaysReturned()} + * @see DatabaseMetaData#generatedKeyAlwaysReturned() + */ + public static boolean generatedKeyAlwaysReturned(final DatabaseMetaData databaseMetaData) throws SQLException { + try { + return databaseMetaData.generatedKeyAlwaysReturned(); + } catch (final AbstractMethodError e) { + // do nothing + return false; + } + } + + /** + * Delegates to {@link Connection#getNetworkTimeout()} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Connection#getNetworkTimeout()}, then return 0. + *

+ * + * @param connection + * the receiver + * @return See {@link Connection#getNetworkTimeout()} + * @throws SQLException + * See {@link Connection#getNetworkTimeout()} + * @see Connection#getNetworkTimeout() + */ + public static int getNetworkTimeout(final Connection connection) throws SQLException { + try { + return connection.getNetworkTimeout(); + } catch (final AbstractMethodError e) { + return 0; + } + } + + /** + * Delegates to {@link ResultSet#getObject(int, Class)} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link ResultSet#getObject(int, Class)}, then return 0. + *

+ * + * @param + * See {@link ResultSet#getObject(int, Class)} + * @param resultSet + * See {@link ResultSet#getObject(int, Class)} + * @param columnIndex + * See {@link ResultSet#getObject(int, Class)} + * @param type + * See {@link ResultSet#getObject(int, Class)} + * @return See {@link ResultSet#getObject(int, Class)} + * @throws SQLException + * See {@link ResultSet#getObject(int, Class)} + * @see ResultSet#getObject(int, Class) + */ + @SuppressWarnings("unchecked") + public static T getObject(final ResultSet resultSet, final int columnIndex, final Class type) + throws SQLException { + try { + return resultSet.getObject(columnIndex, type); + } catch (final AbstractMethodError e) { + if (type == String.class) { + return (T) resultSet.getString(columnIndex); + } + // Numbers + if (type == Integer.class) { + return (T) Integer.valueOf(resultSet.getInt(columnIndex)); + } + if (type == Long.class) { + return (T) Long.valueOf(resultSet.getLong(columnIndex)); + } + if (type == Double.class) { + return (T) Double.valueOf(resultSet.getDouble(columnIndex)); + } + if (type == Float.class) { + return (T) Float.valueOf(resultSet.getFloat(columnIndex)); + } + if (type == Short.class) { + return (T) Short.valueOf(resultSet.getShort(columnIndex)); + } + if (type == BigDecimal.class) { + return (T) resultSet.getBigDecimal(columnIndex); + } + if (type == Byte.class) { + return (T) Byte.valueOf(resultSet.getByte(columnIndex)); + } + // Dates + if (type == Date.class) { + return (T) resultSet.getDate(columnIndex); + } + if (type == Time.class) { + return (T) resultSet.getTime(columnIndex); + } + if (type == Timestamp.class) { + return (T) resultSet.getTimestamp(columnIndex); + } + // Streams + if (type == InputStream.class) { + return (T) resultSet.getBinaryStream(columnIndex); + } + if (type == Reader.class) { + return (T) resultSet.getCharacterStream(columnIndex); + } + // Other + if (type == Object.class) { + return (T) resultSet.getObject(columnIndex); + } + if (type == Boolean.class) { + return (T) Boolean.valueOf(resultSet.getBoolean(columnIndex)); + } + if (type == Array.class) { + return (T) resultSet.getArray(columnIndex); + } + if (type == Blob.class) { + return (T) resultSet.getBlob(columnIndex); + } + if (type == Clob.class) { + return (T) resultSet.getClob(columnIndex); + } + if (type == Ref.class) { + return (T) resultSet.getRef(columnIndex); + } + if (type == RowId.class) { + return (T) resultSet.getRowId(columnIndex); + } + if (type == SQLXML.class) { + return (T) resultSet.getSQLXML(columnIndex); + } + if (type == URL.class) { + return (T) resultSet.getURL(columnIndex); + } + throw new SQLFeatureNotSupportedException( + String.format("resultSet=%s, columnIndex=%,d, type=%s", resultSet, columnIndex, type)); + } + } + + /** + * Delegates to {@link ResultSet#getObject(String, Class)} without throwing an {@link AbstractMethodError}. + * + * @param + * See {@link ResultSet#getObject(String, Class)} + * @param resultSet + * See {@link ResultSet#getObject(String, Class)} + * @param columnLabel + * See {@link ResultSet#getObject(String, Class)} + * @param type + * See {@link ResultSet#getObject(String, Class)} + * @return See {@link ResultSet#getObject(String, Class)} + * @throws SQLException + * See {@link ResultSet#getObject(String, Class)} + * @see ResultSet#getObject(int, Class) + */ + @SuppressWarnings("unchecked") + public static T getObject(final ResultSet resultSet, final String columnLabel, final Class type) + throws SQLException { + try { + return resultSet.getObject(columnLabel, type); + } catch (final AbstractMethodError e) { + // Numbers + if (type == Integer.class) { + return (T) Integer.valueOf(resultSet.getInt(columnLabel)); + } + if (type == Long.class) { + return (T) Long.valueOf(resultSet.getLong(columnLabel)); + } + if (type == Double.class) { + return (T) Double.valueOf(resultSet.getDouble(columnLabel)); + } + if (type == Float.class) { + return (T) Float.valueOf(resultSet.getFloat(columnLabel)); + } + if (type == Short.class) { + return (T) Short.valueOf(resultSet.getShort(columnLabel)); + } + if (type == BigDecimal.class) { + return (T) resultSet.getBigDecimal(columnLabel); + } + if (type == Byte.class) { + return (T) Byte.valueOf(resultSet.getByte(columnLabel)); + } + // Dates + if (type == Date.class) { + return (T) resultSet.getDate(columnLabel); + } + if (type == Time.class) { + return (T) resultSet.getTime(columnLabel); + } + if (type == Timestamp.class) { + return (T) resultSet.getTimestamp(columnLabel); + } + // Streams + if (type == InputStream.class) { + return (T) resultSet.getBinaryStream(columnLabel); + } + if (type == Reader.class) { + return (T) resultSet.getCharacterStream(columnLabel); + } + // Other + if (type == Object.class) { + return (T) resultSet.getObject(columnLabel); + } + if (type == Boolean.class) { + return (T) Boolean.valueOf(resultSet.getBoolean(columnLabel)); + } + if (type == Array.class) { + return (T) resultSet.getArray(columnLabel); + } + if (type == Blob.class) { + return (T) resultSet.getBlob(columnLabel); + } + if (type == Clob.class) { + return (T) resultSet.getClob(columnLabel); + } + if (type == Ref.class) { + return (T) resultSet.getRef(columnLabel); + } + if (type == RowId.class) { + return (T) resultSet.getRowId(columnLabel); + } + if (type == SQLXML.class) { + return (T) resultSet.getSQLXML(columnLabel); + } + if (type == URL.class) { + return (T) resultSet.getURL(columnLabel); + } + throw new SQLFeatureNotSupportedException( + String.format("resultSet=%s, columnLabel=%s, type=%s", resultSet, columnLabel, type)); + } + } + + /** + * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null. + *

+ * + * @param commonDataSource + * See {@link CommonDataSource#getParentLogger()} + * @return See {@link CommonDataSource#getParentLogger()} + * @throws SQLFeatureNotSupportedException + * See {@link CommonDataSource#getParentLogger()} + */ + public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException { + try { + return commonDataSource.getParentLogger(); + } catch (final AbstractMethodError e) { + throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()"); + } + } + + /** + * Delegates to {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} without throwing a + * {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}, + * then return null. + *

+ * + * @param databaseMetaData + * the receiver + * @param catalog + * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @param schemaPattern + * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @param tableNamePattern + * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @param columnNamePattern + * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @return See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @throws SQLException + * See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} + * @see DatabaseMetaData#getPseudoColumns(String, String, String, String) + */ + public static ResultSet getPseudoColumns(final DatabaseMetaData databaseMetaData, final String catalog, + final String schemaPattern, final String tableNamePattern, final String columnNamePattern) + throws SQLException { + try { + return databaseMetaData.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); + } catch (final AbstractMethodError e) { + // do nothing + return null; + } + } + + /** + * Delegates to {@link Connection#getSchema()} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Connection#getSchema()}, then return null. + *

+ * + * @param connection + * the receiver + * @return null for a JDBC 4 driver or a value per {@link Connection#getSchema()}. + * @throws SQLException + * See {@link Connection#getSchema()}. + * @see Connection#getSchema() + */ + public static String getSchema(final Connection connection) throws SQLException { + try { + return connection.getSchema(); + } catch (final AbstractMethodError e) { + // do nothing + return null; + } + } + + /** + * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the + * connection is closed to then throw an SQLException. + *

+ * + * @param statement + * See {@link Statement#isCloseOnCompletion()} + * @return See {@link Statement#isCloseOnCompletion()} + * @throws SQLException + * See {@link Statement#isCloseOnCompletion()} + * @see Statement#closeOnCompletion() + */ + public static boolean isCloseOnCompletion(final Statement statement) throws SQLException { + try { + return statement.isCloseOnCompletion(); + } catch (final AbstractMethodError e) { + if (statement.isClosed()) { + throw new SQLException("Statement closed"); + } + return false; + } + } + + /** + * Delegates to {@link Connection#setNetworkTimeout(Executor, int)} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Connection#setNetworkTimeout(Executor, int)}, then do nothing. + *

+ * + * @param connection + * the receiver + * @param executor + * See {@link Connection#setNetworkTimeout(Executor, int)} + * @param milliseconds + * {@link Connection#setNetworkTimeout(Executor, int)} + * @throws SQLException + * {@link Connection#setNetworkTimeout(Executor, int)} + * @see Connection#setNetworkTimeout(Executor, int) + */ + public static void setNetworkTimeout(final Connection connection, final Executor executor, final int milliseconds) + throws SQLException { + try { + connection.setNetworkTimeout(executor, milliseconds); + } catch (final AbstractMethodError ignored) { + // do nothing + } + } + + /** + * Delegates to {@link Connection#setSchema(String)} without throwing an {@link AbstractMethodError}. + *

+ * If the JDBC driver does not implement {@link Connection#setSchema(String)}, then do nothing. + *

+ * + * @param connection + * the receiver + * @param schema + * See {@link Connection#setSchema(String)}. + * @throws SQLException + * See {@link Connection#setSchema(String)}. + * @see Connection#setSchema(String) + */ + public static void setSchema(final Connection connection, final String schema) throws SQLException { + try { + connection.setSchema(schema); + } catch (final AbstractMethodError ignored) { + // do nothing + } + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/LifetimeExceededException.java b/src/main/java/org/apache/commons/dbcp2/LifetimeExceededException.java index cfc2bec940..01f14b8486 100644 --- a/src/main/java/org/apache/commons/dbcp2/LifetimeExceededException.java +++ b/src/main/java/org/apache/commons/dbcp2/LifetimeExceededException.java @@ -1,44 +1,44 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.SQLException; - -/** - * Exception thrown when a connection's maximum lifetime has been exceeded. - * - * @since 2.1 - */ -final class LifetimeExceededException extends SQLException { - - private static final long serialVersionUID = -3783783104516492659L; - - /** - * Constructs a new instance. - */ - public LifetimeExceededException() { - } - - /** - * Constructs a new instance with the given message. - * - * @param reason a description of the exception - */ - public LifetimeExceededException(final String reason) { - super(reason); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.SQLException; + +/** + * Exception thrown when a connection's maximum lifetime has been exceeded. + * + * @since 2.1 + */ +final class LifetimeExceededException extends SQLException { + + private static final long serialVersionUID = -3783783104516492659L; + + /** + * Constructs a new instance. + */ + public LifetimeExceededException() { + } + + /** + * Constructs a new instance with the given message. + * + * @param reason a description of the exception + */ + public LifetimeExceededException(final String reason) { + super(reason); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/ObjectNameWrapper.java b/src/main/java/org/apache/commons/dbcp2/ObjectNameWrapper.java index 189b79e5c8..76d77e9bd6 100644 --- a/src/main/java/org/apache/commons/dbcp2/ObjectNameWrapper.java +++ b/src/main/java/org/apache/commons/dbcp2/ObjectNameWrapper.java @@ -1,104 +1,104 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.lang.management.ManagementFactory; -import java.util.Objects; - -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Internal wrapper class that allows JMX to be a noop if absent or disabled. - * - * @since 2.2.1 - */ -final class ObjectNameWrapper { - - private static final Log log = LogFactory.getLog(ObjectNameWrapper.class); - - private static final MBeanServer MBEAN_SERVER = getPlatformMBeanServer(); - - private static MBeanServer getPlatformMBeanServer() { - try { - return ManagementFactory.getPlatformMBeanServer(); - } catch (final LinkageError | Exception e) { - // ignore - JMX not available - log.debug("Failed to get platform MBeanServer", e); - return null; - } - } - - public static ObjectName unwrap(final ObjectNameWrapper wrapper) { - return wrapper == null ? null : wrapper.unwrap(); - } - - public static ObjectNameWrapper wrap(final ObjectName objectName) { - return new ObjectNameWrapper(objectName); - } - - public static ObjectNameWrapper wrap(final String name) throws MalformedObjectNameException { - return wrap(new ObjectName(name)); - } - - private final ObjectName objectName; - - public ObjectNameWrapper(final ObjectName objectName) { - this.objectName = objectName; - } - - public void registerMBean(final Object object) { - if (MBEAN_SERVER == null || objectName == null) { - return; - } - try { - MBEAN_SERVER.registerMBean(object, objectName); - } catch (final LinkageError | Exception e) { - log.warn("Failed to complete JMX registration for " + objectName, e); - } - } - - /** - * @since 2.7.0 - */ - @Override - public String toString() { - return Objects.toString(objectName); - } - - public void unregisterMBean() { - if (MBEAN_SERVER == null || objectName == null) { - return; - } - if (MBEAN_SERVER.isRegistered(objectName)) { - try { - MBEAN_SERVER.unregisterMBean(objectName); - } catch (final LinkageError | Exception e) { - log.warn("Failed to complete JMX unregistration for " + objectName, e); - } - } - } - - public ObjectName unwrap() { - return objectName; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.lang.management.ManagementFactory; +import java.util.Objects; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Internal wrapper class that allows JMX to be a noop if absent or disabled. + * + * @since 2.2.1 + */ +final class ObjectNameWrapper { + + private static final Log log = LogFactory.getLog(ObjectNameWrapper.class); + + private static final MBeanServer MBEAN_SERVER = getPlatformMBeanServer(); + + private static MBeanServer getPlatformMBeanServer() { + try { + return ManagementFactory.getPlatformMBeanServer(); + } catch (final LinkageError | Exception e) { + // ignore - JMX not available + log.debug("Failed to get platform MBeanServer", e); + return null; + } + } + + public static ObjectName unwrap(final ObjectNameWrapper wrapper) { + return wrapper == null ? null : wrapper.unwrap(); + } + + public static ObjectNameWrapper wrap(final ObjectName objectName) { + return new ObjectNameWrapper(objectName); + } + + public static ObjectNameWrapper wrap(final String name) throws MalformedObjectNameException { + return wrap(new ObjectName(name)); + } + + private final ObjectName objectName; + + public ObjectNameWrapper(final ObjectName objectName) { + this.objectName = objectName; + } + + public void registerMBean(final Object object) { + if (MBEAN_SERVER == null || objectName == null) { + return; + } + try { + MBEAN_SERVER.registerMBean(object, objectName); + } catch (final LinkageError | Exception e) { + log.warn("Failed to complete JMX registration for " + objectName, e); + } + } + + /** + * @since 2.7.0 + */ + @Override + public String toString() { + return Objects.toString(objectName); + } + + public void unregisterMBean() { + if (MBEAN_SERVER == null || objectName == null) { + return; + } + if (MBEAN_SERVER.isRegistered(objectName)) { + try { + MBEAN_SERVER.unregisterMBean(objectName); + } catch (final LinkageError | Exception e) { + log.warn("Failed to complete JMX unregistration for " + objectName, e); + } + } + } + + public ObjectName unwrap() { + return objectName; + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java index c5551ae4a6..6e1ec6d857 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java @@ -1,461 +1,461 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.lang.management.ManagementFactory; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Duration; -import java.util.Collection; -import java.util.concurrent.Executor; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import javax.management.InstanceAlreadyExistsException; -import javax.management.MBeanRegistrationException; -import javax.management.MBeanServer; -import javax.management.NotCompliantMBeanException; -import javax.management.ObjectName; - -import org.apache.commons.pool2.ObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPool; - -/** - * A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool} - * when closed. - * - * @since 2.0 - */ -public class PoolableConnection extends DelegatingConnection implements PoolableConnectionMXBean { - - private static MBeanServer MBEAN_SERVER; - - static { - try { - MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); - } catch (final NoClassDefFoundError | Exception ignored) { - // ignore - JMX not available - } - } - - /** The pool to which I should return. */ - private final ObjectPool pool; - - private final ObjectNameWrapper jmxObjectName; - - // Use a prepared statement for validation, retaining the last used SQL to - // check if the validation query has changed. - private PreparedStatement validationPreparedStatement; - private String lastValidationSql; - - /** - * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be - * considered broken and not pass validation in the future. - */ - private boolean fatalSqlExceptionThrown; - - /** - * SQL State codes considered to signal fatal conditions. Overrides the defaults in - * {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). - */ - private final Collection disconnectionSqlCodes; - - /** - * A collection of SQL State codes that are not considered fatal disconnection codes. - * - * @since 2.13.0 - */ - private final Collection disconnectionIgnoreSqlCodes; - - /** Whether or not to fast fail validation after fatal connection errors */ - private final boolean fastFailValidation; - - private final Lock lock = new ReentrantLock(); - - /** - * - * @param conn - * my underlying connection - * @param pool - * the pool to which I should return when closed - * @param jmxName - * JMX name - */ - public PoolableConnection(final Connection conn, final ObjectPool pool, - final ObjectName jmxName) { - this(conn, pool, jmxName, null, true); - } - - /** - * - * @param conn - * my underlying connection - * @param pool - * the pool to which I should return when closed - * @param jmxObjectName - * JMX name - * @param disconnectSqlCodes - * SQL State codes considered fatal disconnection errors - * @param fastFailValidation - * true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to - * run query or isValid) - */ - public PoolableConnection(final Connection conn, final ObjectPool pool, - final ObjectName jmxObjectName, final Collection disconnectSqlCodes, - final boolean fastFailValidation) { - this(conn, pool, jmxObjectName, disconnectSqlCodes, null, fastFailValidation); - } - - /** - * Creates a new {@link PoolableConnection} instance. - * - * @param conn - * my underlying connection - * @param pool - * the pool to which I should return when closed - * @param jmxObjectName - * JMX name - * @param disconnectSqlCodes - * SQL State codes considered fatal disconnection errors - * @param disconnectionIgnoreSqlCodes - * SQL State codes that should be ignored when determining fatal disconnection errors - * @param fastFailValidation - * true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to - * run query or isValid) - * @since 2.13.0 - */ - public PoolableConnection(final Connection conn, final ObjectPool pool, - final ObjectName jmxObjectName, final Collection disconnectSqlCodes, - final Collection disconnectionIgnoreSqlCodes, final boolean fastFailValidation) { - super(conn); - this.pool = pool; - this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName); - this.disconnectionSqlCodes = disconnectSqlCodes; - this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes; - this.fastFailValidation = fastFailValidation; - - if (jmxObjectName != null) { - try { - MBEAN_SERVER.registerMBean(this, jmxObjectName); - } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) { - // For now, simply skip registration - } - } - } - - /** - * Abort my underlying {@link Connection}. - * - * @since 2.9.0 - */ - @Override - public void abort(final Executor executor) throws SQLException { - if (jmxObjectName != null) { - jmxObjectName.unregisterMBean(); - } - super.abort(executor); - } - - /** - * Returns this instance to my containing pool. - */ - @Override - public void close() throws SQLException { - lock.lock(); - try { - if (isClosedInternal()) { - // already closed - return; - } - - boolean isUnderlyingConnectionClosed; - try { - isUnderlyingConnectionClosed = getDelegateInternal().isClosed(); - } catch (final SQLException e) { - try { - pool.invalidateObject(this); - } catch (final IllegalStateException ise) { - // pool is closed, so close the connection - passivate(); - getInnermostDelegate().close(); - } catch (final Exception ignored) { - // DO NOTHING the original exception will be rethrown - } - throw new SQLException("Cannot close connection (isClosed check failed)", e); - } - - /* - * Can't set close before this code block since the connection needs to be open when validation runs. Can't set - * close after this code block since by then the connection will have been returned to the pool and may have - * been borrowed by another thread. Therefore, the close flag is set in passivate(). - */ - if (isUnderlyingConnectionClosed) { - // Abnormal close: underlying connection closed unexpectedly, so we - // must destroy this proxy - try { - pool.invalidateObject(this); - } catch (final IllegalStateException e) { - // pool is closed, so close the connection - passivate(); - getInnermostDelegate().close(); - } catch (final Exception e) { - throw new SQLException("Cannot close connection (invalidating pooled object failed)", e); - } - } else { - // Normal close: underlying connection is still open, so we - // simply need to return this proxy to the pool - try { - pool.returnObject(this); - } catch (final IllegalStateException e) { - // pool is closed, so close the connection - passivate(); - getInnermostDelegate().close(); - } catch (final SQLException | RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Cannot close connection (return to pool failed)", e); - } - } - } finally { - lock.unlock(); - } - } - - /** - * @return The disconnection SQL codes. - * @since 2.6.0 - */ - public Collection getDisconnectionSqlCodes() { - return disconnectionSqlCodes; - } - - /** - * Gets the value of the {@link #toString()} method via a bean getter, so it can be read as a property via JMX. - */ - @Override - public String getToString() { - return toString(); - } - - @Override - protected void handleException(final SQLException e) throws SQLException { - fatalSqlExceptionThrown |= isFatalException(e); - super.handleException(e); - } - - /** - * {@inheritDoc} - *

- * This method should not be used by a client to determine whether or not a connection should be return to the - * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool - * once it is no longer required. - */ - @Override - public boolean isClosed() throws SQLException { - if (isClosedInternal()) { - return true; - } - - if (getDelegateInternal().isClosed()) { - // Something has gone wrong. The underlying connection has been - // closed without the connection being returned to the pool. Return - // it now. - close(); - return true; - } - - return false; - } - - /** - * Checks the SQLState of the input exception. - *

- * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal - * exception codes. If this property is not set, codes are compared against the default codes in - * {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link - * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. Additionally, any SQL state - * listed in {@link #disconnectionIgnoreSqlCodes} will be ignored and not treated as fatal. - *

- * - * @param e SQLException to be examined - * @return true if the exception signals a disconnection - */ - boolean isDisconnectionSqlException(final SQLException e) { - boolean fatalException = false; - final String sqlState = e.getSQLState(); - if (sqlState != null) { - if (disconnectionIgnoreSqlCodes != null && disconnectionIgnoreSqlCodes.contains(sqlState)) { - return false; - } - fatalException = disconnectionSqlCodes == null - ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.isDisconnectionSqlCode(sqlState) - : disconnectionSqlCodes.contains(sqlState); - } - return fatalException; - } - - /** - * @return Whether to fail-fast. - * @since 2.6.0 - */ - public boolean isFastFailValidation() { - return fastFailValidation; - } - - /** - * Checks the SQLState of the input exception and any nested SQLExceptions it wraps. - *

- * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the - * configured list of fatal exception codes. If this property is not set, codes are compared against the default - * codes in {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link - * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. - *

- * - * @param e - * SQLException to be examined - * @return true if the exception signals a disconnection - */ - boolean isFatalException(final SQLException e) { - boolean fatalException = isDisconnectionSqlException(e); - if (!fatalException) { - SQLException parentException = e; - SQLException nextException = e.getNextException(); - while (nextException != null && nextException != parentException && !fatalException) { - fatalException = isDisconnectionSqlException(nextException); - parentException = nextException; - nextException = parentException.getNextException(); - } - } - return fatalException; - } - - @Override - protected void passivate() throws SQLException { - super.passivate(); - setClosedInternal(true); - if (getDelegateInternal() instanceof PoolingConnection) { - ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool(); - } - } - - /** - * Closes the underlying {@link Connection}. - */ - @Override - public void reallyClose() throws SQLException { - if (jmxObjectName != null) { - jmxObjectName.unregisterMBean(); - } - - if (validationPreparedStatement != null) { - Utils.closeQuietly((AutoCloseable) validationPreparedStatement); - } - - super.closeInternal(); - } - - @Override - public void setLastUsed() { - super.setLastUsed(); - if (pool instanceof GenericObjectPool) { - final GenericObjectPool gop = (GenericObjectPool) pool; - if (gop.isAbandonedConfig()) { - gop.use(this); - } - } - } - - /** - * Validates the connection, using the following algorithm: - *
    - *
  1. If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously - * thrown a fatal disconnection exception, a {@code SQLException} is thrown.
  2. - *
  3. If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it - * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.
  4. - *
  5. If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at - * least one row, this method returns successfully. If not, {@code SQLException} is thrown.
  6. - *
- * - * @param sql - * The validation SQL query. - * @param timeoutDuration - * The validation timeout in seconds. - * @throws SQLException - * Thrown when validation fails or an SQLException occurs during validation - * @since 2.10.0 - */ - public void validate(final String sql, Duration timeoutDuration) throws SQLException { - if (fastFailValidation && fatalSqlExceptionThrown) { - throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail")); - } - - if (sql == null || sql.isEmpty()) { - if (timeoutDuration.isNegative()) { - timeoutDuration = Duration.ZERO; - } - if (!isValid(timeoutDuration)) { - throw new SQLException("isValid() returned false"); - } - return; - } - - if (!sql.equals(lastValidationSql)) { - lastValidationSql = sql; - // Has to be the innermost delegate else the prepared statement will - // be closed when the pooled connection is passivated. - validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql); - } - - if (timeoutDuration.compareTo(Duration.ZERO) > 0) { - validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds()); - } - - try (ResultSet rs = validationPreparedStatement.executeQuery()) { - if (!rs.next()) { - throw new SQLException("validationQuery didn't return a row"); - } - } catch (final SQLException sqle) { - throw sqle; - } - } - - /** - * Validates the connection, using the following algorithm: - *
    - *
  1. If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously - * thrown a fatal disconnection exception, a {@code SQLException} is thrown.
  2. - *
  3. If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it - * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.
  4. - *
  5. If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at - * least one row, this method returns successfully. If not, {@code SQLException} is thrown.
  6. - *
- * - * @param sql - * The validation SQL query. - * @param timeoutSeconds - * The validation timeout in seconds. - * @throws SQLException - * Thrown when validation fails or an SQLException occurs during validation - * @deprecated Use {@link #validate(String, Duration)}. - */ - @Deprecated - public void validate(final String sql, final int timeoutSeconds) throws SQLException { - validate(sql, Duration.ofSeconds(timeoutSeconds)); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.lang.management.ManagementFactory; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; + +/** + * A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool} + * when closed. + * + * @since 2.0 + */ +public class PoolableConnection extends DelegatingConnection implements PoolableConnectionMXBean { + + private static MBeanServer MBEAN_SERVER; + + static { + try { + MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); + } catch (final NoClassDefFoundError | Exception ignored) { + // ignore - JMX not available + } + } + + /** The pool to which I should return. */ + private final ObjectPool pool; + + private final ObjectNameWrapper jmxObjectName; + + // Use a prepared statement for validation, retaining the last used SQL to + // check if the validation query has changed. + private PreparedStatement validationPreparedStatement; + private String lastValidationSql; + + /** + * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be + * considered broken and not pass validation in the future. + */ + private boolean fatalSqlExceptionThrown; + + /** + * SQL State codes considered to signal fatal conditions. Overrides the defaults in + * {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). + */ + private final Collection disconnectionSqlCodes; + + /** + * A collection of SQL State codes that are not considered fatal disconnection codes. + * + * @since 2.13.0 + */ + private final Collection disconnectionIgnoreSqlCodes; + + /** Whether or not to fast fail validation after fatal connection errors */ + private final boolean fastFailValidation; + + private final Lock lock = new ReentrantLock(); + + /** + * + * @param conn + * my underlying connection + * @param pool + * the pool to which I should return when closed + * @param jmxName + * JMX name + */ + public PoolableConnection(final Connection conn, final ObjectPool pool, + final ObjectName jmxName) { + this(conn, pool, jmxName, null, true); + } + + /** + * + * @param conn + * my underlying connection + * @param pool + * the pool to which I should return when closed + * @param jmxObjectName + * JMX name + * @param disconnectSqlCodes + * SQL State codes considered fatal disconnection errors + * @param fastFailValidation + * true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to + * run query or isValid) + */ + public PoolableConnection(final Connection conn, final ObjectPool pool, + final ObjectName jmxObjectName, final Collection disconnectSqlCodes, + final boolean fastFailValidation) { + this(conn, pool, jmxObjectName, disconnectSqlCodes, null, fastFailValidation); + } + + /** + * Creates a new {@link PoolableConnection} instance. + * + * @param conn + * my underlying connection + * @param pool + * the pool to which I should return when closed + * @param jmxObjectName + * JMX name + * @param disconnectSqlCodes + * SQL State codes considered fatal disconnection errors + * @param disconnectionIgnoreSqlCodes + * SQL State codes that should be ignored when determining fatal disconnection errors + * @param fastFailValidation + * true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to + * run query or isValid) + * @since 2.13.0 + */ + public PoolableConnection(final Connection conn, final ObjectPool pool, + final ObjectName jmxObjectName, final Collection disconnectSqlCodes, + final Collection disconnectionIgnoreSqlCodes, final boolean fastFailValidation) { + super(conn); + this.pool = pool; + this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName); + this.disconnectionSqlCodes = disconnectSqlCodes; + this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes; + this.fastFailValidation = fastFailValidation; + + if (jmxObjectName != null) { + try { + MBEAN_SERVER.registerMBean(this, jmxObjectName); + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) { + // For now, simply skip registration + } + } + } + + /** + * Abort my underlying {@link Connection}. + * + * @since 2.9.0 + */ + @Override + public void abort(final Executor executor) throws SQLException { + if (jmxObjectName != null) { + jmxObjectName.unregisterMBean(); + } + super.abort(executor); + } + + /** + * Returns this instance to my containing pool. + */ + @Override + public void close() throws SQLException { + lock.lock(); + try { + if (isClosedInternal()) { + // already closed + return; + } + + boolean isUnderlyingConnectionClosed; + try { + isUnderlyingConnectionClosed = getDelegateInternal().isClosed(); + } catch (final SQLException e) { + try { + pool.invalidateObject(this); + } catch (final IllegalStateException ise) { + // pool is closed, so close the connection + passivate(); + getInnermostDelegate().close(); + } catch (final Exception ignored) { + // DO NOTHING the original exception will be rethrown + } + throw new SQLException("Cannot close connection (isClosed check failed)", e); + } + + /* + * Can't set close before this code block since the connection needs to be open when validation runs. Can't set + * close after this code block since by then the connection will have been returned to the pool and may have + * been borrowed by another thread. Therefore, the close flag is set in passivate(). + */ + if (isUnderlyingConnectionClosed) { + // Abnormal close: underlying connection closed unexpectedly, so we + // must destroy this proxy + try { + pool.invalidateObject(this); + } catch (final IllegalStateException e) { + // pool is closed, so close the connection + passivate(); + getInnermostDelegate().close(); + } catch (final Exception e) { + throw new SQLException("Cannot close connection (invalidating pooled object failed)", e); + } + } else { + // Normal close: underlying connection is still open, so we + // simply need to return this proxy to the pool + try { + pool.returnObject(this); + } catch (final IllegalStateException e) { + // pool is closed, so close the connection + passivate(); + getInnermostDelegate().close(); + } catch (final SQLException | RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Cannot close connection (return to pool failed)", e); + } + } + } finally { + lock.unlock(); + } + } + + /** + * @return The disconnection SQL codes. + * @since 2.6.0 + */ + public Collection getDisconnectionSqlCodes() { + return disconnectionSqlCodes; + } + + /** + * Gets the value of the {@link #toString()} method via a bean getter, so it can be read as a property via JMX. + */ + @Override + public String getToString() { + return toString(); + } + + @Override + protected void handleException(final SQLException e) throws SQLException { + fatalSqlExceptionThrown |= isFatalException(e); + super.handleException(e); + } + + /** + * {@inheritDoc} + *

+ * This method should not be used by a client to determine whether or not a connection should be return to the + * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool + * once it is no longer required. + */ + @Override + public boolean isClosed() throws SQLException { + if (isClosedInternal()) { + return true; + } + + if (getDelegateInternal().isClosed()) { + // Something has gone wrong. The underlying connection has been + // closed without the connection being returned to the pool. Return + // it now. + close(); + return true; + } + + return false; + } + + /** + * Checks the SQLState of the input exception. + *

+ * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal + * exception codes. If this property is not set, codes are compared against the default codes in + * {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link + * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. Additionally, any SQL state + * listed in {@link #disconnectionIgnoreSqlCodes} will be ignored and not treated as fatal. + *

+ * + * @param e SQLException to be examined + * @return true if the exception signals a disconnection + */ + boolean isDisconnectionSqlException(final SQLException e) { + boolean fatalException = false; + final String sqlState = e.getSQLState(); + if (sqlState != null) { + if (disconnectionIgnoreSqlCodes != null && disconnectionIgnoreSqlCodes.contains(sqlState)) { + return false; + } + fatalException = disconnectionSqlCodes == null + ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.isDisconnectionSqlCode(sqlState) + : disconnectionSqlCodes.contains(sqlState); + } + return fatalException; + } + + /** + * @return Whether to fail-fast. + * @since 2.6.0 + */ + public boolean isFastFailValidation() { + return fastFailValidation; + } + + /** + * Checks the SQLState of the input exception and any nested SQLExceptions it wraps. + *

+ * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the + * configured list of fatal exception codes. If this property is not set, codes are compared against the default + * codes in {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link + * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. + *

+ * + * @param e + * SQLException to be examined + * @return true if the exception signals a disconnection + */ + boolean isFatalException(final SQLException e) { + boolean fatalException = isDisconnectionSqlException(e); + if (!fatalException) { + SQLException parentException = e; + SQLException nextException = e.getNextException(); + while (nextException != null && nextException != parentException && !fatalException) { + fatalException = isDisconnectionSqlException(nextException); + parentException = nextException; + nextException = parentException.getNextException(); + } + } + return fatalException; + } + + @Override + protected void passivate() throws SQLException { + super.passivate(); + setClosedInternal(true); + if (getDelegateInternal() instanceof PoolingConnection) { + ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool(); + } + } + + /** + * Closes the underlying {@link Connection}. + */ + @Override + public void reallyClose() throws SQLException { + if (jmxObjectName != null) { + jmxObjectName.unregisterMBean(); + } + + if (validationPreparedStatement != null) { + Utils.closeQuietly((AutoCloseable) validationPreparedStatement); + } + + super.closeInternal(); + } + + @Override + public void setLastUsed() { + super.setLastUsed(); + if (pool instanceof GenericObjectPool) { + final GenericObjectPool gop = (GenericObjectPool) pool; + if (gop.isAbandonedConfig()) { + gop.use(this); + } + } + } + + /** + * Validates the connection, using the following algorithm: + *
    + *
  1. If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously + * thrown a fatal disconnection exception, a {@code SQLException} is thrown.
  2. + *
  3. If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it + * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.
  4. + *
  5. If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at + * least one row, this method returns successfully. If not, {@code SQLException} is thrown.
  6. + *
+ * + * @param sql + * The validation SQL query. + * @param timeoutDuration + * The validation timeout in seconds. + * @throws SQLException + * Thrown when validation fails or an SQLException occurs during validation + * @since 2.10.0 + */ + public void validate(final String sql, Duration timeoutDuration) throws SQLException { + if (fastFailValidation && fatalSqlExceptionThrown) { + throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail")); + } + + if (sql == null || sql.isEmpty()) { + if (timeoutDuration.isNegative()) { + timeoutDuration = Duration.ZERO; + } + if (!isValid(timeoutDuration)) { + throw new SQLException("isValid() returned false"); + } + return; + } + + if (!sql.equals(lastValidationSql)) { + lastValidationSql = sql; + // Has to be the innermost delegate else the prepared statement will + // be closed when the pooled connection is passivated. + validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql); + } + + if (timeoutDuration.compareTo(Duration.ZERO) > 0) { + validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds()); + } + + try (ResultSet rs = validationPreparedStatement.executeQuery()) { + if (!rs.next()) { + throw new SQLException("validationQuery didn't return a row"); + } + } catch (final SQLException sqle) { + throw sqle; + } + } + + /** + * Validates the connection, using the following algorithm: + *
    + *
  1. If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously + * thrown a fatal disconnection exception, a {@code SQLException} is thrown.
  2. + *
  3. If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it + * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.
  4. + *
  5. If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at + * least one row, this method returns successfully. If not, {@code SQLException} is thrown.
  6. + *
+ * + * @param sql + * The validation SQL query. + * @param timeoutSeconds + * The validation timeout in seconds. + * @throws SQLException + * Thrown when validation fails or an SQLException occurs during validation + * @deprecated Use {@link #validate(String, Duration)}. + */ + @Deprecated + public void validate(final String sql, final int timeoutSeconds) throws SQLException { + validate(sql, Duration.ofSeconds(timeoutSeconds)); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java index 0b7cdf6868..708389f68b 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java @@ -1,873 +1,873 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; -import java.time.Duration; -import java.util.Collection; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.commons.pool2.DestroyMode; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.ObjectPool; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.PooledObjectFactory; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.apache.commons.pool2.impl.GenericKeyedObjectPool; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; - -/** - * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s. - * - * @since 2.0 - */ -public class PoolableConnectionFactory implements PooledObjectFactory { - - private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class); - - /** - * Internal constant to indicate the level is not set. - */ - static final int UNKNOWN_TRANSACTION_ISOLATION = -1; - - private final ConnectionFactory connectionFactory; - - private final ObjectName dataSourceJmxObjectName; - - private volatile String validationQuery; - - private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); - - private Collection connectionInitSqls; - - private Collection disconnectionSqlCodes; - - private Collection disconnectionIgnoreSqlCodes; - - private boolean fastFailValidation = true; - - private volatile ObjectPool pool; - - private Boolean defaultReadOnly; - - private Boolean defaultAutoCommit; - - private boolean autoCommitOnReturn = true; - - private boolean rollbackOnReturn = true; - - private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION; - - private String defaultCatalog; - - private String defaultSchema; - - private boolean cacheState; - - private boolean poolStatements; - - private boolean clearStatementPoolOnReturn; - - private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; - - private Duration maxConnDuration = Duration.ofMillis(-1); - - private final AtomicLong connectionIndex = new AtomicLong(); - - private Duration defaultQueryTimeoutDuration; - - /** - * Creates a new {@code PoolableConnectionFactory}. - * - * @param connFactory - * the {@link ConnectionFactory} from which to obtain base {@link Connection}s - * @param dataSourceJmxObjectName - * The JMX object name, may be null. - */ - public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) { - this.connectionFactory = connFactory; - this.dataSourceJmxObjectName = dataSourceJmxObjectName; - } - - @Override - public void activateObject(final PooledObject p) throws SQLException { - - validateLifetime(p); - - final PoolableConnection pConnection = p.getObject(); - pConnection.activate(); - - if (defaultAutoCommit != null && pConnection.getAutoCommit() != defaultAutoCommit) { - pConnection.setAutoCommit(defaultAutoCommit); - } - if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION - && pConnection.getTransactionIsolation() != defaultTransactionIsolation) { - pConnection.setTransactionIsolation(defaultTransactionIsolation); - } - if (defaultReadOnly != null && pConnection.isReadOnly() != defaultReadOnly) { - pConnection.setReadOnly(defaultReadOnly); - } - if (defaultCatalog != null && !defaultCatalog.equals(pConnection.getCatalog())) { - pConnection.setCatalog(defaultCatalog); - } - if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(pConnection))) { - Jdbc41Bridge.setSchema(pConnection, defaultSchema); - } - pConnection.setDefaultQueryTimeout(defaultQueryTimeoutDuration); - } - - @Override - public void destroyObject(final PooledObject p) throws SQLException { - p.getObject().reallyClose(); - } - - /** - * @since 2.9.0 - */ - @Override - public void destroyObject(final PooledObject p, final DestroyMode mode) throws SQLException { - if (mode == DestroyMode.ABANDONED) { - Jdbc41Bridge.abort(p.getObject().getInnermostDelegate(), Runnable::run); - } else { - p.getObject().reallyClose(); - } - } - - /** - * Gets the cache state to propagate in {@link #makeObject()}. - * - * @return The cache state. - * @since 2.6.0. - */ - public boolean getCacheState() { - return cacheState; - } - - /** - * Gets the connection factory. - * - * @return The connection factory. - * @since 2.6.0. - */ - public ConnectionFactory getConnectionFactory() { - return connectionFactory; - } - - /** - * Gets how many connections were created in {@link #makeObject()}. - * - * @return the connection count. - */ - protected AtomicLong getConnectionIndex() { - return connectionIndex; - } - - /** - * Gets the collection of initialization SQL statements. - * - * @return The collection of initialization SQL statements. - * @since 2.6.0 - */ - public Collection getConnectionInitSqls() { - return connectionInitSqls; - } - - /** - * Gets data source JMX ObjectName. - * - * @return The data source JMX ObjectName. - * @since 2.6.0. - */ - public ObjectName getDataSourceJmxName() { - return dataSourceJmxObjectName; - } - - /** - * Gets the data source JMX ObjectName. - * - * @return The data source JMX ObjectName. - * @since 2.6.0 - */ - public ObjectName getDataSourceJmxObjectName() { - return dataSourceJmxObjectName; - } - - /** - * Gets the Default auto-commit value. - * - * @return The default auto-commit value. - * @since 2.6.0 - */ - public Boolean getDefaultAutoCommit() { - return defaultAutoCommit; - } - - /** - * Gets the default catalog. - * - * @return The default catalog. - * @since 2.6.0 - */ - public String getDefaultCatalog() { - return defaultCatalog; - } - - /** - * Gets the default query timeout in seconds. - * - * @return The default query timeout in seconds. - * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. - */ - @Deprecated - public Integer getDefaultQueryTimeout() { - return getDefaultQueryTimeoutSeconds(); - } - - /** - * Gets the default query timeout Duration. - * - * @return The default query timeout Duration. - * @since 2.10.0 - */ - public Duration getDefaultQueryTimeoutDuration() { - return defaultQueryTimeoutDuration; - } - - /** - * Gets the default query timeout in seconds. - * - * @return The default query timeout in seconds. - * @since 2.6.0 - * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. - */ - @Deprecated - public Integer getDefaultQueryTimeoutSeconds() { - return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); - } - - /** - * Gets the default read-only-value. - * - * @return The default read-only-value. - * @since 2.6.0 - */ - public Boolean getDefaultReadOnly() { - return defaultReadOnly; - } - - /** - * Gets the default schema. - * - * @return The default schema. - * @since 2.6.0 - */ - public String getDefaultSchema() { - return defaultSchema; - } - - /** - * Gets the default transaction isolation. - * - * @return The default transaction isolation. - * @since 2.6.0 - */ - public int getDefaultTransactionIsolation() { - return defaultTransactionIsolation; - } - - /** - * Gets the collection of SQL State codes that are not considered fatal disconnection codes. - *

- * This method returns the collection of SQL State codes that have been set to be ignored when - * determining if a {@link SQLException} signals a disconnection. These codes are excluded from - * being treated as fatal even if they match the typical disconnection criteria. - *

- * - * @return a {@link Collection} of SQL State codes that should be ignored for disconnection checks. - * @since 2.13.0 - */ - public Collection getDisconnectionIgnoreSqlCodes() { - return disconnectionIgnoreSqlCodes; - } - - /** - * Gets SQL State codes considered to signal fatal conditions. - *

- * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with - * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is - * {@code true}, whenever connections created by this factory generate exceptions with SQL State codes in this list, - * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or - * validation query). - *

- *

- * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. - *

- * - * @return SQL State codes overriding defaults - * @since 2.1 - */ - public Collection getDisconnectionSqlCodes() { - return disconnectionSqlCodes; - } - - /** - * Gets the Maximum connection duration. - * - * @return Maximum connection duration. - * @since 2.10.0 - */ - public Duration getMaxConnDuration() { - return maxConnDuration; - } - - /** - * Gets the Maximum connection lifetime in milliseconds. - * - * @return Maximum connection lifetime in milliseconds. - * @since 2.6.0 - */ - public long getMaxConnLifetimeMillis() { - return maxConnDuration.toMillis(); - } - - /** - * Gets the maximum number of open prepared statements. - * - * @return The maximum number of open prepared statements. - */ - protected int getMaxOpenPreparedStatements() { - return maxOpenPreparedStatements; - } - - /** - * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. - * - * @return the connection pool - */ - public synchronized ObjectPool getPool() { - return pool; - } - - /** - * Tests whether to pool statements. - * - * @return Whether to pool statements. - * @since 2.6.0. - */ - public boolean getPoolStatements() { - return poolStatements; - } - - /** - * Gets the validation query. - * - * @return Validation query. - * @since 2.6.0 - */ - public String getValidationQuery() { - return validationQuery; - } - - /** - * Gets the query timeout in seconds. - * - * @return Validation query timeout in seconds. - * @since 2.10.0 - */ - public Duration getValidationQueryTimeoutDuration() { - return validationQueryTimeoutDuration; - } - - /** - * Gets the query timeout in seconds. - * - * @return Validation query timeout in seconds. - * @since 2.6.0 - * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. - */ - @Deprecated - public int getValidationQueryTimeoutSeconds() { - return (int) validationQueryTimeoutDuration.getSeconds(); - } - - /** - * Initializes the given connection with the collection of SQL statements set in {@link #setConnectionInitSql(Collection)}. - * - * @param conn the connection to initialize. - * @throws SQLException if a database access error occurs or this method is called on a closed connection. - * @see #setConnectionInitSql(Collection) - */ - protected void initializeConnection(final Connection conn) throws SQLException { - final Collection sqls = connectionInitSqls; - if (conn.isClosed()) { - throw new SQLException("initializeConnection: connection closed"); - } - if (!Utils.isEmpty(sqls)) { - try (Statement statement = conn.createStatement()) { - for (final String sql : sqls) { - statement.execute(Objects.requireNonNull(sql, "null connectionInitSqls element")); - } - } - } - } - - /** - * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * - * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * @since 2.6.0 - */ - public boolean isAutoCommitOnReturn() { - return autoCommitOnReturn; - } - - /** - * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * - * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * @deprecated Use {@link #isAutoCommitOnReturn()}. - */ - @Deprecated - public boolean isEnableAutoCommitOnReturn() { - return autoCommitOnReturn; - } - - /** - * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with - * SQL State indicating fatal disconnection errors. - * - * @return true if connections created by this factory will fast fail validation. - * @see #setDisconnectionSqlCodes(Collection) - * @since 2.1 - * @since 2.5.0 Defaults to true, previous versions defaulted to false. - */ - public boolean isFastFailValidation() { - return fastFailValidation; - } - - /** - * Tests whether to rollback on return. - * - * @return Whether to rollback on return. - */ - public boolean isRollbackOnReturn() { - return rollbackOnReturn; - } - - @Override - public PooledObject makeObject() throws SQLException { - Connection conn = connectionFactory.createConnection(); - if (conn == null) { - throw new IllegalStateException("Connection factory returned null from createConnection"); - } - try { - initializeConnection(conn); - } catch (final SQLException e) { - // Make sure the connection is closed - Utils.closeQuietly((AutoCloseable) conn); - // Rethrow original exception so it is visible to caller - throw e; - } - - final long connIndex = connectionIndex.getAndIncrement(); - - if (poolStatements) { - conn = new PoolingConnection(conn); - final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); - config.setMaxTotalPerKey(-1); - config.setBlockWhenExhausted(false); - config.setMaxWait(Duration.ZERO); - config.setMaxIdlePerKey(1); - config.setMaxTotal(maxOpenPreparedStatements); - if (dataSourceJmxObjectName != null) { - final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); - base.append(Constants.JMX_CONNECTION_BASE_EXT); - base.append(connIndex); - config.setJmxNameBase(base.toString()); - config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); - } else { - config.setJmxEnabled(false); - } - final PoolingConnection poolingConn = (PoolingConnection) conn; - final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>(poolingConn, config); - poolingConn.setStatementPool(stmtPool); - poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); - poolingConn.setCacheState(cacheState); - } - - // Register this connection with JMX - final ObjectName connJmxName; - if (dataSourceJmxObjectName == null) { - connJmxName = null; - } else { - final String name = dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex; - try { - connJmxName = new ObjectName(name); - } catch (final MalformedObjectNameException e) { - Utils.closeQuietly((AutoCloseable) conn); - throw new SQLException(name, e); - } - } - - final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, - disconnectionSqlCodes, disconnectionIgnoreSqlCodes, fastFailValidation); - pc.setCacheState(cacheState); - - return new DefaultPooledObject<>(pc); - } - - @Override - public void passivateObject(final PooledObject p) throws SQLException { - - validateLifetime(p); - - final PoolableConnection conn = p.getObject(); - Boolean connAutoCommit = null; - if (rollbackOnReturn) { - connAutoCommit = conn.getAutoCommit(); - if (!connAutoCommit && !conn.isReadOnly()) { - conn.rollback(); - } - } - - conn.clearWarnings(); - - // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should - // have autoCommit enabled - if (autoCommitOnReturn) { - if (connAutoCommit == null) { - connAutoCommit = conn.getAutoCommit(); - } - if (!connAutoCommit) { - conn.setAutoCommit(true); - } - } - - conn.passivate(); - } - - /** - * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * - * @param autoCommitOnReturn whether to set auto-commit. - */ - public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { - this.autoCommitOnReturn = autoCommitOnReturn; - } - - /** - * Sets the cache state to propagate in {@link #makeObject()}. - * - * @param cacheState the cache state to propagate. - */ - public void setCacheState(final boolean cacheState) { - this.cacheState = cacheState; - } - - /** - * Sets whether the pool of statements (which was enabled with {@link #setPoolStatements(boolean)}) should - * be cleared when the connection is returned to its pool. Default is false. - * - * @param clearStatementPoolOnReturn clear or not - * @since 2.8.0 - */ - public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { - this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; - } - - /** - * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off - * connection initialization. - * - * @param connectionInitSqls - * SQL statement to initialize {@link Connection}s. - */ - public void setConnectionInitSql(final Collection connectionInitSqls) { - this.connectionInitSqls = connectionInitSqls; - } - /** - * Sets the default "auto commit" setting for borrowed {@link Connection}s - * - * @param defaultAutoCommit - * the default "auto commit" setting for borrowed {@link Connection}s - */ - public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { - this.defaultAutoCommit = defaultAutoCommit; - } - - /** - * Sets the default "catalog" setting for borrowed {@link Connection}s - * - * @param defaultCatalog - * the default "catalog" setting for borrowed {@link Connection}s - */ - public void setDefaultCatalog(final String defaultCatalog) { - this.defaultCatalog = defaultCatalog; - } - - /** - * Sets the query timeout Duration. - * - * @param defaultQueryTimeoutDuration the query timeout Duration. - * @since 2.10.0 - */ - public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; - } - - /** - * Sets the query timeout in seconds. - * - * @param defaultQueryTimeoutSeconds the query timeout in seconds. - * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. - */ - @Deprecated - public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { - this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); - } - - /** - * Sets the default "read only" setting for borrowed {@link Connection}s - * - * @param defaultReadOnly - * the default "read only" setting for borrowed {@link Connection}s - */ - public void setDefaultReadOnly(final Boolean defaultReadOnly) { - this.defaultReadOnly = defaultReadOnly; - } - - /** - * Sets the default "schema" setting for borrowed {@link Connection}s - * - * @param defaultSchema - * the default "schema" setting for borrowed {@link Connection}s - * @since 2.5.0 - */ - public void setDefaultSchema(final String defaultSchema) { - this.defaultSchema = defaultSchema; - } - - /** - * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s - * - * @param defaultTransactionIsolation - * the default "Transaction Isolation" setting for returned {@link Connection}s - */ - public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { - this.defaultTransactionIsolation = defaultTransactionIsolation; - } - - /** - * Sets the disconnection SQL codes to ignore. - * - * @param disconnectionIgnoreSqlCodes - * The collection of SQL State codes to be ignored. - * @see #getDisconnectionIgnoreSqlCodes() - * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}. - * @since 2.13.0 - */ - public void setDisconnectionIgnoreSqlCodes(final Collection disconnectionIgnoreSqlCodes) { - Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes); - this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes; - } - - /** - * Sets the disconnection SQL codes. - * - * @param disconnectionSqlCodes - * The disconnection SQL codes. - * @see #getDisconnectionSqlCodes() - * @since 2.1 - * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}. - */ - public void setDisconnectionSqlCodes(final Collection disconnectionSqlCodes) { - Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes); - this.disconnectionSqlCodes = disconnectionSqlCodes; - } - - /** - * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}. - * - * @param autoCommitOnReturn whether to set auto-commit. - */ - @Deprecated - public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { - this.autoCommitOnReturn = autoCommitOnReturn; - } - - /** - * @see #isFastFailValidation() - * @param fastFailValidation - * true means connections created by this factory will fast fail validation - * @since 2.1 - */ - public void setFastFailValidation(final boolean fastFailValidation) { - this.fastFailValidation = fastFailValidation; - } - - /** - * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, - * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. - * - * @param maxConnDuration - * The maximum lifetime in milliseconds. - * @since 2.10.0 - */ - public void setMaxConn(final Duration maxConnDuration) { - this.maxConnDuration = maxConnDuration; - } - - /** - * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, - * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. - * - * @param maxConnLifetimeMillis - * The maximum lifetime in milliseconds. - * @deprecated Use {@link #setMaxConn(Duration)}. - */ - @Deprecated - public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { - this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis); - } - - /** - * Sets the maximum number of open prepared statements. - * - * @param maxOpenPreparedStatements - * The maximum number of open prepared statements. - */ - public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { - this.maxOpenPreparedStatements = maxOpenPreparedStatements; - } - - /** - * Deprecated due to typo in method name. - * - * @param maxOpenPreparedStatements - * The maximum number of open prepared statements. - * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. - */ - @Deprecated // Due to typo in method name. - public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { - setMaxOpenPreparedStatements(maxOpenPreparedStatements); - } - - /** - * Sets the {@link ObjectPool} in which to pool {@link Connection}s. - * - * @param pool - * the {@link ObjectPool} in which to pool those {@link Connection}s - */ - public synchronized void setPool(final ObjectPool pool) { - if (null != this.pool && pool != this.pool) { - Utils.closeQuietly(this.pool); - } - this.pool = pool; - } - - /** - * Sets whether to pool statements. - * - * @param poolStatements whether to pool statements. - */ - public void setPoolStatements(final boolean poolStatements) { - this.poolStatements = poolStatements; - } - - /** - * Sets whether to rollback on return. - * - * @param rollbackOnReturn whether to rollback on return. - */ - public void setRollbackOnReturn(final boolean rollbackOnReturn) { - this.rollbackOnReturn = rollbackOnReturn; - } - - /** - * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If - * not specified, {@link Connection#isValid(int)} will be used to validate connections. - * - * @param validationQuery - * a query to use to {@link #validateObject validate} {@link Connection}s. - */ - public void setValidationQuery(final String validationQuery) { - this.validationQuery = validationQuery; - } - - /** - * Sets the validation query timeout, the amount of time, that connection validation will wait for a response from the - * database when executing a validation query. Use a value less than or equal to 0 for no timeout. - * - * @param validationQueryTimeoutDuration new validation query timeout duration. - * @since 2.10.0 - */ - public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { - this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; - } - - /** - * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a - * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. - * - * @param validationQueryTimeoutSeconds - * new validation query timeout value in seconds - * @deprecated {@link #setValidationQueryTimeout(Duration)}. - */ - @Deprecated - public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { - this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); - } - - /** - * Validates the given connection if it is open. - * - * @param conn the connection to validate. - * @throws SQLException if the connection is closed or validate fails. - */ - public void validateConnection(final PoolableConnection conn) throws SQLException { - if (conn.isClosed()) { - throw new SQLException("validateConnection: connection closed"); - } - conn.validate(validationQuery, validationQueryTimeoutDuration); - } - - private void validateLifetime(final PooledObject p) throws LifetimeExceededException { - Utils.validateLifetime(p, maxConnDuration); - } - - @Override - public boolean validateObject(final PooledObject p) { - try { - validateLifetime(p); - validateConnection(p.getObject()); - return true; - } catch (final Exception e) { - if (log.isDebugEnabled()) { - log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); - } - return false; - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Duration; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool2.DestroyMode; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.PooledObjectFactory; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s. + * + * @since 2.0 + */ +public class PoolableConnectionFactory implements PooledObjectFactory { + + private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class); + + /** + * Internal constant to indicate the level is not set. + */ + static final int UNKNOWN_TRANSACTION_ISOLATION = -1; + + private final ConnectionFactory connectionFactory; + + private final ObjectName dataSourceJmxObjectName; + + private volatile String validationQuery; + + private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); + + private Collection connectionInitSqls; + + private Collection disconnectionSqlCodes; + + private Collection disconnectionIgnoreSqlCodes; + + private boolean fastFailValidation = true; + + private volatile ObjectPool pool; + + private Boolean defaultReadOnly; + + private Boolean defaultAutoCommit; + + private boolean autoCommitOnReturn = true; + + private boolean rollbackOnReturn = true; + + private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION; + + private String defaultCatalog; + + private String defaultSchema; + + private boolean cacheState; + + private boolean poolStatements; + + private boolean clearStatementPoolOnReturn; + + private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; + + private Duration maxConnDuration = Duration.ofMillis(-1); + + private final AtomicLong connectionIndex = new AtomicLong(); + + private Duration defaultQueryTimeoutDuration; + + /** + * Creates a new {@code PoolableConnectionFactory}. + * + * @param connFactory + * the {@link ConnectionFactory} from which to obtain base {@link Connection}s + * @param dataSourceJmxObjectName + * The JMX object name, may be null. + */ + public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) { + this.connectionFactory = connFactory; + this.dataSourceJmxObjectName = dataSourceJmxObjectName; + } + + @Override + public void activateObject(final PooledObject p) throws SQLException { + + validateLifetime(p); + + final PoolableConnection pConnection = p.getObject(); + pConnection.activate(); + + if (defaultAutoCommit != null && pConnection.getAutoCommit() != defaultAutoCommit) { + pConnection.setAutoCommit(defaultAutoCommit); + } + if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION + && pConnection.getTransactionIsolation() != defaultTransactionIsolation) { + pConnection.setTransactionIsolation(defaultTransactionIsolation); + } + if (defaultReadOnly != null && pConnection.isReadOnly() != defaultReadOnly) { + pConnection.setReadOnly(defaultReadOnly); + } + if (defaultCatalog != null && !defaultCatalog.equals(pConnection.getCatalog())) { + pConnection.setCatalog(defaultCatalog); + } + if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(pConnection))) { + Jdbc41Bridge.setSchema(pConnection, defaultSchema); + } + pConnection.setDefaultQueryTimeout(defaultQueryTimeoutDuration); + } + + @Override + public void destroyObject(final PooledObject p) throws SQLException { + p.getObject().reallyClose(); + } + + /** + * @since 2.9.0 + */ + @Override + public void destroyObject(final PooledObject p, final DestroyMode mode) throws SQLException { + if (mode == DestroyMode.ABANDONED) { + Jdbc41Bridge.abort(p.getObject().getInnermostDelegate(), Runnable::run); + } else { + p.getObject().reallyClose(); + } + } + + /** + * Gets the cache state to propagate in {@link #makeObject()}. + * + * @return The cache state. + * @since 2.6.0. + */ + public boolean getCacheState() { + return cacheState; + } + + /** + * Gets the connection factory. + * + * @return The connection factory. + * @since 2.6.0. + */ + public ConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + /** + * Gets how many connections were created in {@link #makeObject()}. + * + * @return the connection count. + */ + protected AtomicLong getConnectionIndex() { + return connectionIndex; + } + + /** + * Gets the collection of initialization SQL statements. + * + * @return The collection of initialization SQL statements. + * @since 2.6.0 + */ + public Collection getConnectionInitSqls() { + return connectionInitSqls; + } + + /** + * Gets data source JMX ObjectName. + * + * @return The data source JMX ObjectName. + * @since 2.6.0. + */ + public ObjectName getDataSourceJmxName() { + return dataSourceJmxObjectName; + } + + /** + * Gets the data source JMX ObjectName. + * + * @return The data source JMX ObjectName. + * @since 2.6.0 + */ + public ObjectName getDataSourceJmxObjectName() { + return dataSourceJmxObjectName; + } + + /** + * Gets the Default auto-commit value. + * + * @return The default auto-commit value. + * @since 2.6.0 + */ + public Boolean getDefaultAutoCommit() { + return defaultAutoCommit; + } + + /** + * Gets the default catalog. + * + * @return The default catalog. + * @since 2.6.0 + */ + public String getDefaultCatalog() { + return defaultCatalog; + } + + /** + * Gets the default query timeout in seconds. + * + * @return The default query timeout in seconds. + * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. + */ + @Deprecated + public Integer getDefaultQueryTimeout() { + return getDefaultQueryTimeoutSeconds(); + } + + /** + * Gets the default query timeout Duration. + * + * @return The default query timeout Duration. + * @since 2.10.0 + */ + public Duration getDefaultQueryTimeoutDuration() { + return defaultQueryTimeoutDuration; + } + + /** + * Gets the default query timeout in seconds. + * + * @return The default query timeout in seconds. + * @since 2.6.0 + * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}. + */ + @Deprecated + public Integer getDefaultQueryTimeoutSeconds() { + return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds(); + } + + /** + * Gets the default read-only-value. + * + * @return The default read-only-value. + * @since 2.6.0 + */ + public Boolean getDefaultReadOnly() { + return defaultReadOnly; + } + + /** + * Gets the default schema. + * + * @return The default schema. + * @since 2.6.0 + */ + public String getDefaultSchema() { + return defaultSchema; + } + + /** + * Gets the default transaction isolation. + * + * @return The default transaction isolation. + * @since 2.6.0 + */ + public int getDefaultTransactionIsolation() { + return defaultTransactionIsolation; + } + + /** + * Gets the collection of SQL State codes that are not considered fatal disconnection codes. + *

+ * This method returns the collection of SQL State codes that have been set to be ignored when + * determining if a {@link SQLException} signals a disconnection. These codes are excluded from + * being treated as fatal even if they match the typical disconnection criteria. + *

+ * + * @return a {@link Collection} of SQL State codes that should be ignored for disconnection checks. + * @since 2.13.0 + */ + public Collection getDisconnectionIgnoreSqlCodes() { + return disconnectionIgnoreSqlCodes; + } + + /** + * Gets SQL State codes considered to signal fatal conditions. + *

+ * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with + * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is + * {@code true}, whenever connections created by this factory generate exceptions with SQL State codes in this list, + * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or + * validation query). + *

+ *

+ * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. + *

+ * + * @return SQL State codes overriding defaults + * @since 2.1 + */ + public Collection getDisconnectionSqlCodes() { + return disconnectionSqlCodes; + } + + /** + * Gets the Maximum connection duration. + * + * @return Maximum connection duration. + * @since 2.10.0 + */ + public Duration getMaxConnDuration() { + return maxConnDuration; + } + + /** + * Gets the Maximum connection lifetime in milliseconds. + * + * @return Maximum connection lifetime in milliseconds. + * @since 2.6.0 + */ + public long getMaxConnLifetimeMillis() { + return maxConnDuration.toMillis(); + } + + /** + * Gets the maximum number of open prepared statements. + * + * @return The maximum number of open prepared statements. + */ + protected int getMaxOpenPreparedStatements() { + return maxOpenPreparedStatements; + } + + /** + * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. + * + * @return the connection pool + */ + public synchronized ObjectPool getPool() { + return pool; + } + + /** + * Tests whether to pool statements. + * + * @return Whether to pool statements. + * @since 2.6.0. + */ + public boolean getPoolStatements() { + return poolStatements; + } + + /** + * Gets the validation query. + * + * @return Validation query. + * @since 2.6.0 + */ + public String getValidationQuery() { + return validationQuery; + } + + /** + * Gets the query timeout in seconds. + * + * @return Validation query timeout in seconds. + * @since 2.10.0 + */ + public Duration getValidationQueryTimeoutDuration() { + return validationQueryTimeoutDuration; + } + + /** + * Gets the query timeout in seconds. + * + * @return Validation query timeout in seconds. + * @since 2.6.0 + * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. + */ + @Deprecated + public int getValidationQueryTimeoutSeconds() { + return (int) validationQueryTimeoutDuration.getSeconds(); + } + + /** + * Initializes the given connection with the collection of SQL statements set in {@link #setConnectionInitSql(Collection)}. + * + * @param conn the connection to initialize. + * @throws SQLException if a database access error occurs or this method is called on a closed connection. + * @see #setConnectionInitSql(Collection) + */ + protected void initializeConnection(final Connection conn) throws SQLException { + final Collection sqls = connectionInitSqls; + if (conn.isClosed()) { + throw new SQLException("initializeConnection: connection closed"); + } + if (!Utils.isEmpty(sqls)) { + try (Statement statement = conn.createStatement()) { + for (final String sql : sqls) { + statement.execute(Objects.requireNonNull(sql, "null connectionInitSqls element")); + } + } + } + } + + /** + * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * + * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * @since 2.6.0 + */ + public boolean isAutoCommitOnReturn() { + return autoCommitOnReturn; + } + + /** + * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * + * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * @deprecated Use {@link #isAutoCommitOnReturn()}. + */ + @Deprecated + public boolean isEnableAutoCommitOnReturn() { + return autoCommitOnReturn; + } + + /** + * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with + * SQL State indicating fatal disconnection errors. + * + * @return true if connections created by this factory will fast fail validation. + * @see #setDisconnectionSqlCodes(Collection) + * @since 2.1 + * @since 2.5.0 Defaults to true, previous versions defaulted to false. + */ + public boolean isFastFailValidation() { + return fastFailValidation; + } + + /** + * Tests whether to rollback on return. + * + * @return Whether to rollback on return. + */ + public boolean isRollbackOnReturn() { + return rollbackOnReturn; + } + + @Override + public PooledObject makeObject() throws SQLException { + Connection conn = connectionFactory.createConnection(); + if (conn == null) { + throw new IllegalStateException("Connection factory returned null from createConnection"); + } + try { + initializeConnection(conn); + } catch (final SQLException e) { + // Make sure the connection is closed + Utils.closeQuietly((AutoCloseable) conn); + // Rethrow original exception so it is visible to caller + throw e; + } + + final long connIndex = connectionIndex.getAndIncrement(); + + if (poolStatements) { + conn = new PoolingConnection(conn); + final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); + config.setMaxTotalPerKey(-1); + config.setBlockWhenExhausted(false); + config.setMaxWait(Duration.ZERO); + config.setMaxIdlePerKey(1); + config.setMaxTotal(maxOpenPreparedStatements); + if (dataSourceJmxObjectName != null) { + final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); + base.append(Constants.JMX_CONNECTION_BASE_EXT); + base.append(connIndex); + config.setJmxNameBase(base.toString()); + config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); + } else { + config.setJmxEnabled(false); + } + final PoolingConnection poolingConn = (PoolingConnection) conn; + final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>(poolingConn, config); + poolingConn.setStatementPool(stmtPool); + poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); + poolingConn.setCacheState(cacheState); + } + + // Register this connection with JMX + final ObjectName connJmxName; + if (dataSourceJmxObjectName == null) { + connJmxName = null; + } else { + final String name = dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex; + try { + connJmxName = new ObjectName(name); + } catch (final MalformedObjectNameException e) { + Utils.closeQuietly((AutoCloseable) conn); + throw new SQLException(name, e); + } + } + + final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, + disconnectionSqlCodes, disconnectionIgnoreSqlCodes, fastFailValidation); + pc.setCacheState(cacheState); + + return new DefaultPooledObject<>(pc); + } + + @Override + public void passivateObject(final PooledObject p) throws SQLException { + + validateLifetime(p); + + final PoolableConnection conn = p.getObject(); + Boolean connAutoCommit = null; + if (rollbackOnReturn) { + connAutoCommit = conn.getAutoCommit(); + if (!connAutoCommit && !conn.isReadOnly()) { + conn.rollback(); + } + } + + conn.clearWarnings(); + + // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should + // have autoCommit enabled + if (autoCommitOnReturn) { + if (connAutoCommit == null) { + connAutoCommit = conn.getAutoCommit(); + } + if (!connAutoCommit) { + conn.setAutoCommit(true); + } + } + + conn.passivate(); + } + + /** + * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * + * @param autoCommitOnReturn whether to set auto-commit. + */ + public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { + this.autoCommitOnReturn = autoCommitOnReturn; + } + + /** + * Sets the cache state to propagate in {@link #makeObject()}. + * + * @param cacheState the cache state to propagate. + */ + public void setCacheState(final boolean cacheState) { + this.cacheState = cacheState; + } + + /** + * Sets whether the pool of statements (which was enabled with {@link #setPoolStatements(boolean)}) should + * be cleared when the connection is returned to its pool. Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; + } + + /** + * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off + * connection initialization. + * + * @param connectionInitSqls + * SQL statement to initialize {@link Connection}s. + */ + public void setConnectionInitSql(final Collection connectionInitSqls) { + this.connectionInitSqls = connectionInitSqls; + } + /** + * Sets the default "auto commit" setting for borrowed {@link Connection}s + * + * @param defaultAutoCommit + * the default "auto commit" setting for borrowed {@link Connection}s + */ + public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { + this.defaultAutoCommit = defaultAutoCommit; + } + + /** + * Sets the default "catalog" setting for borrowed {@link Connection}s + * + * @param defaultCatalog + * the default "catalog" setting for borrowed {@link Connection}s + */ + public void setDefaultCatalog(final String defaultCatalog) { + this.defaultCatalog = defaultCatalog; + } + + /** + * Sets the query timeout Duration. + * + * @param defaultQueryTimeoutDuration the query timeout Duration. + * @since 2.10.0 + */ + public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration; + } + + /** + * Sets the query timeout in seconds. + * + * @param defaultQueryTimeoutSeconds the query timeout in seconds. + * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}. + */ + @Deprecated + public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { + this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds); + } + + /** + * Sets the default "read only" setting for borrowed {@link Connection}s + * + * @param defaultReadOnly + * the default "read only" setting for borrowed {@link Connection}s + */ + public void setDefaultReadOnly(final Boolean defaultReadOnly) { + this.defaultReadOnly = defaultReadOnly; + } + + /** + * Sets the default "schema" setting for borrowed {@link Connection}s + * + * @param defaultSchema + * the default "schema" setting for borrowed {@link Connection}s + * @since 2.5.0 + */ + public void setDefaultSchema(final String defaultSchema) { + this.defaultSchema = defaultSchema; + } + + /** + * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s + * + * @param defaultTransactionIsolation + * the default "Transaction Isolation" setting for returned {@link Connection}s + */ + public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { + this.defaultTransactionIsolation = defaultTransactionIsolation; + } + + /** + * Sets the disconnection SQL codes to ignore. + * + * @param disconnectionIgnoreSqlCodes + * The collection of SQL State codes to be ignored. + * @see #getDisconnectionIgnoreSqlCodes() + * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}. + * @since 2.13.0 + */ + public void setDisconnectionIgnoreSqlCodes(final Collection disconnectionIgnoreSqlCodes) { + Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes); + this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes; + } + + /** + * Sets the disconnection SQL codes. + * + * @param disconnectionSqlCodes + * The disconnection SQL codes. + * @see #getDisconnectionSqlCodes() + * @since 2.1 + * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}. + */ + public void setDisconnectionSqlCodes(final Collection disconnectionSqlCodes) { + Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes); + this.disconnectionSqlCodes = disconnectionSqlCodes; + } + + /** + * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}. + * + * @param autoCommitOnReturn whether to set auto-commit. + */ + @Deprecated + public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { + this.autoCommitOnReturn = autoCommitOnReturn; + } + + /** + * @see #isFastFailValidation() + * @param fastFailValidation + * true means connections created by this factory will fast fail validation + * @since 2.1 + */ + public void setFastFailValidation(final boolean fastFailValidation) { + this.fastFailValidation = fastFailValidation; + } + + /** + * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, + * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. + * + * @param maxConnDuration + * The maximum lifetime in milliseconds. + * @since 2.10.0 + */ + public void setMaxConn(final Duration maxConnDuration) { + this.maxConnDuration = maxConnDuration; + } + + /** + * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, + * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. + * + * @param maxConnLifetimeMillis + * The maximum lifetime in milliseconds. + * @deprecated Use {@link #setMaxConn(Duration)}. + */ + @Deprecated + public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { + this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis); + } + + /** + * Sets the maximum number of open prepared statements. + * + * @param maxOpenPreparedStatements + * The maximum number of open prepared statements. + */ + public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { + this.maxOpenPreparedStatements = maxOpenPreparedStatements; + } + + /** + * Deprecated due to typo in method name. + * + * @param maxOpenPreparedStatements + * The maximum number of open prepared statements. + * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. + */ + @Deprecated // Due to typo in method name. + public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { + setMaxOpenPreparedStatements(maxOpenPreparedStatements); + } + + /** + * Sets the {@link ObjectPool} in which to pool {@link Connection}s. + * + * @param pool + * the {@link ObjectPool} in which to pool those {@link Connection}s + */ + public synchronized void setPool(final ObjectPool pool) { + if (null != this.pool && pool != this.pool) { + Utils.closeQuietly(this.pool); + } + this.pool = pool; + } + + /** + * Sets whether to pool statements. + * + * @param poolStatements whether to pool statements. + */ + public void setPoolStatements(final boolean poolStatements) { + this.poolStatements = poolStatements; + } + + /** + * Sets whether to rollback on return. + * + * @param rollbackOnReturn whether to rollback on return. + */ + public void setRollbackOnReturn(final boolean rollbackOnReturn) { + this.rollbackOnReturn = rollbackOnReturn; + } + + /** + * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If + * not specified, {@link Connection#isValid(int)} will be used to validate connections. + * + * @param validationQuery + * a query to use to {@link #validateObject validate} {@link Connection}s. + */ + public void setValidationQuery(final String validationQuery) { + this.validationQuery = validationQuery; + } + + /** + * Sets the validation query timeout, the amount of time, that connection validation will wait for a response from the + * database when executing a validation query. Use a value less than or equal to 0 for no timeout. + * + * @param validationQueryTimeoutDuration new validation query timeout duration. + * @since 2.10.0 + */ + public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { + this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; + } + + /** + * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a + * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. + * + * @param validationQueryTimeoutSeconds + * new validation query timeout value in seconds + * @deprecated {@link #setValidationQueryTimeout(Duration)}. + */ + @Deprecated + public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { + this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); + } + + /** + * Validates the given connection if it is open. + * + * @param conn the connection to validate. + * @throws SQLException if the connection is closed or validate fails. + */ + public void validateConnection(final PoolableConnection conn) throws SQLException { + if (conn.isClosed()) { + throw new SQLException("validateConnection: connection closed"); + } + conn.validate(validationQuery, validationQueryTimeoutDuration); + } + + private void validateLifetime(final PooledObject p) throws LifetimeExceededException { + Utils.validateLifetime(p, maxConnDuration); + } + + @Override + public boolean validateObject(final PooledObject p) { + try { + validateLifetime(p); + validateConnection(p.getObject()); + return true; + } catch (final Exception e) { + if (log.isDebugEnabled()) { + log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); + } + return false; + } + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java index f394164831..6fc99cc24c 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java @@ -1,646 +1,646 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.NoSuchElementException; -import java.util.Objects; - -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.KeyedPooledObjectFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.apache.commons.pool2.impl.GenericKeyedObjectPool; - -/** - * A {@link DelegatingConnection} that pools {@link PreparedStatement}s. - *

- * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each - * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of - * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See - * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.) - *

- * - * @see PoolablePreparedStatement - * @since 2.0 - */ -public class PoolingConnection extends DelegatingConnection - implements KeyedPooledObjectFactory { - - /** - * Statement types. - * - * See subclasses of {@link Statement}. - * - * @since 2.0 protected enum. - * @since 2.4.0 public enum. - * @see Statement - * @see CallableStatement - * @see PreparedStatement - */ - public enum StatementType { - - /** - * Callable statement. - * - * @see CallableStatement - */ - CALLABLE_STATEMENT, - - /** - * Prepared statement. - * - * @see PreparedStatement - */ - PREPARED_STATEMENT - } - - /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */ - private KeyedObjectPool pStmtPool; - - private boolean clearStatementPoolOnReturn; - - /** - * Constructs a new instance. - * - * @param connection - * the underlying {@link Connection}. - */ - public PoolingConnection(final Connection connection) { - super(connection); - } - - /** - * {@link KeyedPooledObjectFactory} method for activating pooled statements. - * - * @param key - * ignored - * @param pooledObject - * wrapped pooled statement to be activated - */ - @Override - public void activateObject(final PStmtKey key, final PooledObject pooledObject) - throws SQLException { - pooledObject.getObject().activate(); - } - - /** - * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the - * underlying connection. - */ - @Override - public synchronized void close() throws SQLException { - try { - if (null != pStmtPool) { - final KeyedObjectPool oldPool = pStmtPool; - pStmtPool = null; - try { - oldPool.close(); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Cannot close connection", e); - } - } - } finally { - try { - @SuppressWarnings("resource") - final Connection delegateInternal = getDelegateInternal(); - if (delegateInternal != null) { - delegateInternal.close(); - } - } finally { - setClosedInternal(true); - } - } - } - - /** - * Notification from {@link PoolableConnection} that we returned to the pool. - * - * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be - * cleared - * @since 2.8.0 - */ - public void connectionReturnedToPool() throws SQLException { - if (pStmtPool != null && clearStatementPoolOnReturn) { - try { - pStmtPool.clear(); - } catch (final Exception e) { - throw new SQLException("Error clearing statement pool", e); - } - } - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull()); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param autoGeneratedKeys - * A flag indicating whether auto-generated keys should be returned; one of - * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @param resultSetHoldability - * result set holdability - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, - resultSetHoldability); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @param resultSetHoldability - * result set holdability - * @param statementType - * statement type - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability, final StatementType statementType) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, - resultSetHoldability, statementType); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @param statementType - * statement type - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, - final StatementType statementType) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param columnIndexes - * An array of column indexes indicating the columns that should be returned from the inserted row or - * rows. - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final int[] columnIndexes) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param statementType - * statement type - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final StatementType statementType) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null); - } - - /** - * Creates a PStmtKey for the given arguments. - * - * @param sql - * the SQL string used to define the statement - * @param columnNames - * column names - * - * @return the PStmtKey created for the given arguments. - */ - protected PStmtKey createKey(final String sql, final String[] columnNames) { - return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames); - } - - /** - * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements. - * Closes the underlying statement. - * - * @param key - * ignored - * @param pooledObject - * the wrapped pooled statement to be destroyed. - */ - @Override - public void destroyObject(final PStmtKey key, final PooledObject pooledObject) throws SQLException { - if (pooledObject != null) { - @SuppressWarnings("resource") - final DelegatingPreparedStatement object = pooledObject.getObject(); - if (object != null) { - @SuppressWarnings("resource") - final Statement innermostDelegate = object.getInnermostDelegate(); - if (innermostDelegate != null) { - innermostDelegate.close(); - } - } - } - } - - private String getCatalogOrNull() { - try { - return getCatalog(); - } catch (final SQLException ignored) { - return null; - } - } - - private String getSchemaOrNull() { - try { - return getSchema(); - } catch (final SQLException ignored) { - return null; - } - } - - /** - * Gets the prepared statement pool. - * - * @return statement pool - * @since 2.8.0 - */ - public KeyedObjectPool getStatementPool() { - return pStmtPool; - } - - /** - * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or - * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a - * PoolablePreparedStatement or PoolableCallableStatement is created. - * - * @param key - * the key for the {@link PreparedStatement} to be created - * @see #createKey(String, int, int, StatementType) - */ - @SuppressWarnings("resource") - @Override - public PooledObject makeObject(final PStmtKey key) throws SQLException { - if (null == key) { - throw new IllegalArgumentException("Prepared statement key is null or invalid."); - } - if (key.getStmtType() == StatementType.PREPARED_STATEMENT) { - final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate()); - @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this - final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, this); - return new DefaultPooledObject<>(pps); - } - final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate()); - final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, this); - return new DefaultPooledObject<>(pcs); - } - - /** - * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original. - * - * @param sql The statement to be normalized. - * @return The canonical form of the supplied SQL statement. - */ - protected String normalizeSQL(final String sql) { - return sql.trim(); - } - - /** - * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s. - * Invokes {@link PreparedStatement#clearParameters}. - * - * @param key - * ignored - * @param pooledObject - * a wrapped {@link PreparedStatement} - */ - @Override - public void passivateObject(final PStmtKey key, final PooledObject pooledObject) - throws SQLException { - @SuppressWarnings("resource") - final DelegatingPreparedStatement dps = pooledObject.getObject(); - dps.clearParameters(); - dps.passivate(); - } - - /** - * Creates or obtains a {@link CallableStatement} from the pool. - * - * @param key - * a {@link PStmtKey} for the given arguments - * @return a {@link PoolableCallableStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - private CallableStatement prepareCall(final PStmtKey key) throws SQLException { - return (CallableStatement) prepareStatement(key); - } - - /** - * Creates or obtains a {@link CallableStatement} from the pool. - * - * @param sql - * the SQL string used to define the CallableStatement - * @return a {@link PoolableCallableStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public CallableStatement prepareCall(final String sql) throws SQLException { - return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT)); - } - - /** - * Creates or obtains a {@link CallableStatement} from the pool. - * - * @param sql - * the SQL string used to define the CallableStatement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @return a {@link PoolableCallableStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); - } - - /** - * Creates or obtains a {@link CallableStatement} from the pool. - * - * @param sql - * the SQL string used to define the CallableStatement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @param resultSetHoldability - * result set holdability - * @return a {@link PoolableCallableStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, - resultSetHoldability, StatementType.CALLABLE_STATEMENT)); - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param key - * a {@link PStmtKey} for the given arguments - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException { - if (null == pStmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pStmtPool.borrowObject(key); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql) throws SQLException { - return prepareStatement(createKey(sql)); - } - - /* - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @param autoGeneratedKeys - * A flag indicating whether auto-generated keys should be returned; one of - * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { - return prepareStatement(createKey(sql, autoGeneratedKeys)); - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency)); - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @param resultSetType - * result set type - * @param resultSetConcurrency - * result set concurrency - * @param resultSetHoldability - * result set holdability - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @param columnIndexes - * An array of column indexes indicating the columns that should be returned from the inserted row or - * rows. - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { - return prepareStatement(createKey(sql, columnIndexes)); - } - - /** - * Creates or obtains a {@link PreparedStatement} from the pool. - * - * @param sql - * the SQL string used to define the PreparedStatement - * @param columnNames - * column names - * @return a {@link PoolablePreparedStatement} - * @throws SQLException - * Wraps an underlying exception. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { - return prepareStatement(createKey(sql, columnNames)); - } - - /** - * Sets whether the pool of statements should be cleared when the connection is returned to its pool. - * Default is false. - * - * @param clearStatementPoolOnReturn clear or not - * @since 2.8.0 - */ - public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { - this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; - } - - /** - * Sets the prepared statement pool. - * - * @param pool - * the prepared statement pool. - */ - public void setStatementPool(final KeyedObjectPool pool) { - pStmtPool = pool; - } - - @Override - public synchronized String toString() { - if (pStmtPool instanceof GenericKeyedObjectPool) { - // DBCP-596 PoolingConnection.toString() causes StackOverflowError - final GenericKeyedObjectPool gkop = (GenericKeyedObjectPool) pStmtPool; - if (gkop.getFactory() == this) { - return "PoolingConnection: " + pStmtPool.getClass() + "@" + System.identityHashCode(pStmtPool); - } - } - return "PoolingConnection: " + Objects.toString(pStmtPool); - } - - /** - * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently, always returns true. - * - * @param key - * ignored - * @param pooledObject - * ignored - * @return {@code true} - */ - @Override - public boolean validateObject(final PStmtKey key, final PooledObject pooledObject) { - return true; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.NoSuchElementException; +import java.util.Objects; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; + +/** + * A {@link DelegatingConnection} that pools {@link PreparedStatement}s. + *

+ * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each + * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of + * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See + * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.) + *

+ * + * @see PoolablePreparedStatement + * @since 2.0 + */ +public class PoolingConnection extends DelegatingConnection + implements KeyedPooledObjectFactory { + + /** + * Statement types. + * + * See subclasses of {@link Statement}. + * + * @since 2.0 protected enum. + * @since 2.4.0 public enum. + * @see Statement + * @see CallableStatement + * @see PreparedStatement + */ + public enum StatementType { + + /** + * Callable statement. + * + * @see CallableStatement + */ + CALLABLE_STATEMENT, + + /** + * Prepared statement. + * + * @see PreparedStatement + */ + PREPARED_STATEMENT + } + + /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */ + private KeyedObjectPool pStmtPool; + + private boolean clearStatementPoolOnReturn; + + /** + * Constructs a new instance. + * + * @param connection + * the underlying {@link Connection}. + */ + public PoolingConnection(final Connection connection) { + super(connection); + } + + /** + * {@link KeyedPooledObjectFactory} method for activating pooled statements. + * + * @param key + * ignored + * @param pooledObject + * wrapped pooled statement to be activated + */ + @Override + public void activateObject(final PStmtKey key, final PooledObject pooledObject) + throws SQLException { + pooledObject.getObject().activate(); + } + + /** + * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the + * underlying connection. + */ + @Override + public synchronized void close() throws SQLException { + try { + if (null != pStmtPool) { + final KeyedObjectPool oldPool = pStmtPool; + pStmtPool = null; + try { + oldPool.close(); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Cannot close connection", e); + } + } + } finally { + try { + @SuppressWarnings("resource") + final Connection delegateInternal = getDelegateInternal(); + if (delegateInternal != null) { + delegateInternal.close(); + } + } finally { + setClosedInternal(true); + } + } + } + + /** + * Notification from {@link PoolableConnection} that we returned to the pool. + * + * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be + * cleared + * @since 2.8.0 + */ + public void connectionReturnedToPool() throws SQLException { + if (pStmtPool != null && clearStatementPoolOnReturn) { + try { + pStmtPool.clear(); + } catch (final Exception e) { + throw new SQLException("Error clearing statement pool", e); + } + } + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull()); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param autoGeneratedKeys + * A flag indicating whether auto-generated keys should be returned; one of + * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @param resultSetHoldability + * result set holdability + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, + resultSetHoldability); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @param resultSetHoldability + * result set holdability + * @param statementType + * statement type + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability, final StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, + resultSetHoldability, statementType); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @param statementType + * statement type + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, + final StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param columnIndexes + * An array of column indexes indicating the columns that should be returned from the inserted row or + * rows. + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final int[] columnIndexes) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param statementType + * statement type + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null); + } + + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param columnNames + * column names + * + * @return the PStmtKey created for the given arguments. + */ + protected PStmtKey createKey(final String sql, final String[] columnNames) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames); + } + + /** + * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements. + * Closes the underlying statement. + * + * @param key + * ignored + * @param pooledObject + * the wrapped pooled statement to be destroyed. + */ + @Override + public void destroyObject(final PStmtKey key, final PooledObject pooledObject) throws SQLException { + if (pooledObject != null) { + @SuppressWarnings("resource") + final DelegatingPreparedStatement object = pooledObject.getObject(); + if (object != null) { + @SuppressWarnings("resource") + final Statement innermostDelegate = object.getInnermostDelegate(); + if (innermostDelegate != null) { + innermostDelegate.close(); + } + } + } + } + + private String getCatalogOrNull() { + try { + return getCatalog(); + } catch (final SQLException ignored) { + return null; + } + } + + private String getSchemaOrNull() { + try { + return getSchema(); + } catch (final SQLException ignored) { + return null; + } + } + + /** + * Gets the prepared statement pool. + * + * @return statement pool + * @since 2.8.0 + */ + public KeyedObjectPool getStatementPool() { + return pStmtPool; + } + + /** + * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or + * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a + * PoolablePreparedStatement or PoolableCallableStatement is created. + * + * @param key + * the key for the {@link PreparedStatement} to be created + * @see #createKey(String, int, int, StatementType) + */ + @SuppressWarnings("resource") + @Override + public PooledObject makeObject(final PStmtKey key) throws SQLException { + if (null == key) { + throw new IllegalArgumentException("Prepared statement key is null or invalid."); + } + if (key.getStmtType() == StatementType.PREPARED_STATEMENT) { + final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate()); + @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this + final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, this); + return new DefaultPooledObject<>(pps); + } + final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate()); + final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, this); + return new DefaultPooledObject<>(pcs); + } + + /** + * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original. + * + * @param sql The statement to be normalized. + * @return The canonical form of the supplied SQL statement. + */ + protected String normalizeSQL(final String sql) { + return sql.trim(); + } + + /** + * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s. + * Invokes {@link PreparedStatement#clearParameters}. + * + * @param key + * ignored + * @param pooledObject + * a wrapped {@link PreparedStatement} + */ + @Override + public void passivateObject(final PStmtKey key, final PooledObject pooledObject) + throws SQLException { + @SuppressWarnings("resource") + final DelegatingPreparedStatement dps = pooledObject.getObject(); + dps.clearParameters(); + dps.passivate(); + } + + /** + * Creates or obtains a {@link CallableStatement} from the pool. + * + * @param key + * a {@link PStmtKey} for the given arguments + * @return a {@link PoolableCallableStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + private CallableStatement prepareCall(final PStmtKey key) throws SQLException { + return (CallableStatement) prepareStatement(key); + } + + /** + * Creates or obtains a {@link CallableStatement} from the pool. + * + * @param sql + * the SQL string used to define the CallableStatement + * @return a {@link PoolableCallableStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public CallableStatement prepareCall(final String sql) throws SQLException { + return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT)); + } + + /** + * Creates or obtains a {@link CallableStatement} from the pool. + * + * @param sql + * the SQL string used to define the CallableStatement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @return a {@link PoolableCallableStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); + } + + /** + * Creates or obtains a {@link CallableStatement} from the pool. + * + * @param sql + * the SQL string used to define the CallableStatement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @param resultSetHoldability + * result set holdability + * @return a {@link PoolableCallableStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, + resultSetHoldability, StatementType.CALLABLE_STATEMENT)); + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param key + * a {@link PStmtKey} for the given arguments + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException { + if (null == pStmtPool) { + throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); + } + try { + return pStmtPool.borrowObject(key); + } catch (final NoSuchElementException e) { + throw new SQLException("MaxOpenPreparedStatements limit reached", e); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + return prepareStatement(createKey(sql)); + } + + /* + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param autoGeneratedKeys + * A flag indicating whether auto-generated keys should be returned; one of + * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + return prepareStatement(createKey(sql, autoGeneratedKeys)); + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency)); + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param resultSetType + * result set type + * @param resultSetConcurrency + * result set concurrency + * @param resultSetHoldability + * result set holdability + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param columnIndexes + * An array of column indexes indicating the columns that should be returned from the inserted row or + * rows. + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + return prepareStatement(createKey(sql, columnIndexes)); + } + + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param columnNames + * column names + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + return prepareStatement(createKey(sql, columnNames)); + } + + /** + * Sets whether the pool of statements should be cleared when the connection is returned to its pool. + * Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; + } + + /** + * Sets the prepared statement pool. + * + * @param pool + * the prepared statement pool. + */ + public void setStatementPool(final KeyedObjectPool pool) { + pStmtPool = pool; + } + + @Override + public synchronized String toString() { + if (pStmtPool instanceof GenericKeyedObjectPool) { + // DBCP-596 PoolingConnection.toString() causes StackOverflowError + final GenericKeyedObjectPool gkop = (GenericKeyedObjectPool) pStmtPool; + if (gkop.getFactory() == this) { + return "PoolingConnection: " + pStmtPool.getClass() + "@" + System.identityHashCode(pStmtPool); + } + } + return "PoolingConnection: " + Objects.toString(pStmtPool); + } + + /** + * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently, always returns true. + * + * @param key + * ignored + * @param pooledObject + * ignored + * @return {@code true} + */ + @Override + public boolean validateObject(final PStmtKey key, final PooledObject pooledObject) { + return true; + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java b/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java index d1aacd288c..d254726125 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java @@ -1,270 +1,270 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.DriverPropertyInfo; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.HashMap; -import java.util.NoSuchElementException; -import java.util.Properties; -import java.util.logging.Logger; - -import org.apache.commons.pool2.ObjectPool; - -/** - * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}. - * - * @since 2.0 - */ -public class PoolingDriver implements Driver { - - /** - * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore. - * - * @since 2.0 - */ - private final class PoolGuardConnectionWrapper extends DelegatingConnection { - - private final ObjectPool pool; - - PoolGuardConnectionWrapper(final ObjectPool pool, final Connection delegate) { - super(delegate); - this.pool = pool; - } - - /** - * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate() - */ - @Override - public Connection getDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return super.getDelegate(); - } - return null; - } - - /** - * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate() - */ - @Override - public Connection getInnermostDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return super.getInnermostDelegate(); - } - return null; - } - } - - private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {}; - - /* Register myself with the {@link DriverManager}. */ - static { - try { - DriverManager.registerDriver(new PoolingDriver()); - } catch (final Exception ignored) { - // Ignored - } - } - - /** The map of registered pools. */ - protected static final HashMap> pools = new HashMap<>(); - - /** - * The Apache Commons connection string prefix {@value}. - */ - public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:"; - - /** - * The String length of {@link #URL_PREFIX}. - */ - protected static final int URL_PREFIX_LEN = URL_PREFIX.length(); - - /** - * Major version number. - */ - protected static final int MAJOR_VERSION = 1; - - /** - * Minor version number. - */ - protected static final int MINOR_VERSION = 0; - - /** Controls access to the underlying connection */ - private final boolean accessToUnderlyingConnectionAllowed; - - /** - * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled. - */ - public PoolingDriver() { - this(true); - } - - /** - * For unit testing purposes. - * - * @param accessToUnderlyingConnectionAllowed - * Do {@link DelegatingConnection}s created by this driver permit access to the delegate? - */ - protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) { - this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; - } - - @Override - public boolean acceptsURL(final String url) throws SQLException { - return url != null && url.startsWith(URL_PREFIX); - } - - /** - * Closes a named pool. - * - * @param name - * The pool name. - * @throws SQLException - * Thrown when a problem is caught closing the pool. - */ - public synchronized void closePool(final String name) throws SQLException { - @SuppressWarnings("resource") - final ObjectPool pool = pools.get(name); - if (pool != null) { - pools.remove(name); - try { - pool.close(); - } catch (final Exception e) { - throw new SQLException("Error closing pool " + name, e); - } - } - } - - @Override - public Connection connect(final String url, final Properties info) throws SQLException { - if (acceptsURL(url)) { - final ObjectPool pool = getConnectionPool(url.substring(URL_PREFIX_LEN)); - try { - final Connection conn = pool.borrowObject(); - if (conn == null) { - return null; - } - return new PoolGuardConnectionWrapper(pool, conn); - } catch (final NoSuchElementException e) { - throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e); - } catch (final SQLException | RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e); - } - } - return null; - } - - /** - * Gets the connection pool for the given name. - * - * @param name - * The pool name - * @return The pool - * @throws SQLException - * Thrown when the named pool is not registered. - */ - public synchronized ObjectPool getConnectionPool(final String name) throws SQLException { - final ObjectPool pool = pools.get(name); - if (null == pool) { - throw new SQLException("Pool not registered: " + name); - } - return pool; - } - - @Override - public int getMajorVersion() { - return MAJOR_VERSION; - } - - @Override - public int getMinorVersion() { - return MINOR_VERSION; - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(); - } - - /** - * Gets the pool names. - * - * @return the pool names. - */ - public synchronized String[] getPoolNames() { - return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY); - } - - @Override - public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) { - return EMPTY_DRIVER_PROPERTY_INFO_ARRAY; - } - /** - * Invalidates the given connection. - * - * @param conn - * connection to invalidate - * @throws SQLException - * if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating - * the connection - */ - public void invalidateConnection(final Connection conn) throws SQLException { - if (!(conn instanceof PoolGuardConnectionWrapper)) { - throw new SQLException("Invalid connection class"); - } - final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn; - @SuppressWarnings("unchecked") - final ObjectPool pool = (ObjectPool) pgconn.pool; - try { - pool.invalidateObject(pgconn.getDelegateInternal()); - } catch (final Exception ignored) { - // Ignored. - } - } - - /** - * Returns the value of the accessToUnderlyingConnectionAllowed property. - * - * @return true if access to the underlying is allowed, false otherwise. - */ - protected boolean isAccessToUnderlyingConnectionAllowed() { - return accessToUnderlyingConnectionAllowed; - } - - @Override - public boolean jdbcCompliant() { - return true; - } - - /** - * Registers a named pool. - * - * @param name - * The pool name. - * @param pool - * The pool. - */ - public synchronized void registerPool(final String name, final ObjectPool pool) { - pools.put(name, pool); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.HashMap; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.logging.Logger; + +import org.apache.commons.pool2.ObjectPool; + +/** + * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}. + * + * @since 2.0 + */ +public class PoolingDriver implements Driver { + + /** + * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore. + * + * @since 2.0 + */ + private final class PoolGuardConnectionWrapper extends DelegatingConnection { + + private final ObjectPool pool; + + PoolGuardConnectionWrapper(final ObjectPool pool, final Connection delegate) { + super(delegate); + this.pool = pool; + } + + /** + * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate() + */ + @Override + public Connection getDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return super.getDelegate(); + } + return null; + } + + /** + * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate() + */ + @Override + public Connection getInnermostDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return super.getInnermostDelegate(); + } + return null; + } + } + + private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {}; + + /* Register myself with the {@link DriverManager}. */ + static { + try { + DriverManager.registerDriver(new PoolingDriver()); + } catch (final Exception ignored) { + // Ignored + } + } + + /** The map of registered pools. */ + protected static final HashMap> pools = new HashMap<>(); + + /** + * The Apache Commons connection string prefix {@value}. + */ + public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:"; + + /** + * The String length of {@link #URL_PREFIX}. + */ + protected static final int URL_PREFIX_LEN = URL_PREFIX.length(); + + /** + * Major version number. + */ + protected static final int MAJOR_VERSION = 1; + + /** + * Minor version number. + */ + protected static final int MINOR_VERSION = 0; + + /** Controls access to the underlying connection */ + private final boolean accessToUnderlyingConnectionAllowed; + + /** + * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled. + */ + public PoolingDriver() { + this(true); + } + + /** + * For unit testing purposes. + * + * @param accessToUnderlyingConnectionAllowed + * Do {@link DelegatingConnection}s created by this driver permit access to the delegate? + */ + protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) { + this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; + } + + @Override + public boolean acceptsURL(final String url) throws SQLException { + return url != null && url.startsWith(URL_PREFIX); + } + + /** + * Closes a named pool. + * + * @param name + * The pool name. + * @throws SQLException + * Thrown when a problem is caught closing the pool. + */ + public synchronized void closePool(final String name) throws SQLException { + @SuppressWarnings("resource") + final ObjectPool pool = pools.get(name); + if (pool != null) { + pools.remove(name); + try { + pool.close(); + } catch (final Exception e) { + throw new SQLException("Error closing pool " + name, e); + } + } + } + + @Override + public Connection connect(final String url, final Properties info) throws SQLException { + if (acceptsURL(url)) { + final ObjectPool pool = getConnectionPool(url.substring(URL_PREFIX_LEN)); + try { + final Connection conn = pool.borrowObject(); + if (conn == null) { + return null; + } + return new PoolGuardConnectionWrapper(pool, conn); + } catch (final NoSuchElementException e) { + throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e); + } catch (final SQLException | RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e); + } + } + return null; + } + + /** + * Gets the connection pool for the given name. + * + * @param name + * The pool name + * @return The pool + * @throws SQLException + * Thrown when the named pool is not registered. + */ + public synchronized ObjectPool getConnectionPool(final String name) throws SQLException { + final ObjectPool pool = pools.get(name); + if (null == pool) { + throw new SQLException("Pool not registered: " + name); + } + return pool; + } + + @Override + public int getMajorVersion() { + return MAJOR_VERSION; + } + + @Override + public int getMinorVersion() { + return MINOR_VERSION; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Gets the pool names. + * + * @return the pool names. + */ + public synchronized String[] getPoolNames() { + return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY); + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) { + return EMPTY_DRIVER_PROPERTY_INFO_ARRAY; + } + /** + * Invalidates the given connection. + * + * @param conn + * connection to invalidate + * @throws SQLException + * if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating + * the connection + */ + public void invalidateConnection(final Connection conn) throws SQLException { + if (!(conn instanceof PoolGuardConnectionWrapper)) { + throw new SQLException("Invalid connection class"); + } + final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn; + @SuppressWarnings("unchecked") + final ObjectPool pool = (ObjectPool) pgconn.pool; + try { + pool.invalidateObject(pgconn.getDelegateInternal()); + } catch (final Exception ignored) { + // Ignored. + } + } + + /** + * Returns the value of the accessToUnderlyingConnectionAllowed property. + * + * @return true if access to the underlying is allowed, false otherwise. + */ + protected boolean isAccessToUnderlyingConnectionAllowed() { + return accessToUnderlyingConnectionAllowed; + } + + @Override + public boolean jdbcCompliant() { + return true; + } + + /** + * Registers a named pool. + * + * @param name + * The pool name. + * @param pool + * The pool. + */ + public synchronized void registerPool(final String name, final ObjectPool pool) { + pools.put(name, pool); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/Utils.java b/src/main/java/org/apache/commons/dbcp2/Utils.java index 3020f89339..d3784535c7 100644 --- a/src/main/java/org/apache/commons/dbcp2/Utils.java +++ b/src/main/java/org/apache/commons/dbcp2/Utils.java @@ -1,297 +1,297 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; -import java.text.MessageFormat; -import java.time.Duration; -import java.time.Instant; -import java.util.Collection; -import java.util.HashSet; -import java.util.Properties; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.pool2.PooledObject; - -/** - * Utility methods. - * - * @since 2.0 - */ -public final class Utils { - - private static final ResourceBundle messages = ResourceBundle - .getBundle(Utils.class.getPackage().getName() + ".LocalStrings"); - - /** - * Whether the security manager is enabled. - * - * @deprecated No replacement. - */ - @Deprecated - public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled(); - - /** - * Any SQL State starting with this value is considered a fatal disconnect. - */ - public static final String DISCONNECTION_SQL_CODE_PREFIX = "08"; - - /** - * SQL codes of fatal connection errors. - *
    - *
  • 57P01 (Admin shutdown)
  • - *
  • 57P02 (Crash shutdown)
  • - *
  • 57P03 (Cannot connect now)
  • - *
  • 01002 (SQL92 disconnect error)
  • - *
  • JZ0C0 (Sybase disconnect error)
  • - *
  • JZ0C1 (Sybase disconnect error)
  • - *
- * @deprecated Use {@link #getDisconnectionSqlCodes()}. - */ - @Deprecated - public static final Set DISCONNECTION_SQL_CODES; - - static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {}; - - static final String[] EMPTY_STRING_ARRAY = {}; - static { - DISCONNECTION_SQL_CODES = new HashSet<>(); - DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown - DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown - DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now - DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error - DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error - DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error - } - - /** - * Checks for conflicts between two collections. - *

- * If any overlap is found between the two provided collections, an {@link IllegalArgumentException} is thrown. - *

- * - * @param codes1 The first collection of SQL state codes. - * @param codes2 The second collection of SQL state codes. - * @throws IllegalArgumentException if any codes overlap between the two collections. - * @since 2.13.0 - */ - static void checkSqlCodes(final Collection codes1, final Collection codes2) { - if (codes1 != null && codes2 != null) { - final Set test = new HashSet<>(codes1); - test.retainAll(codes2); - if (!test.isEmpty()) { - throw new IllegalArgumentException(test + " cannot be in both disconnectionSqlCodes and disconnectionIgnoreSqlCodes."); - } - } - } - - /** - * Clones the given char[] if not null. - * - * @param value may be null. - * @return a cloned char[] or null. - */ - public static char[] clone(final char[] value) { - return value == null ? null : value.clone(); - } - - /** - * Clones the given {@link Properties} without the standard "user" or "password" entries. - * - * @param properties may be null - * @return a clone of the input without the standard "user" or "password" entries. - * @since 2.8.0 - */ - public static Properties cloneWithoutCredentials(final Properties properties) { - if (properties != null) { - final Properties temp = (Properties) properties.clone(); - temp.remove(Constants.KEY_USER); - temp.remove(Constants.KEY_PASSWORD); - return temp; - } - return properties; - } - - /** - * Closes the given {@link AutoCloseable} and if an exception is caught, then calls {@code exceptionHandler}. - * - * @param autoCloseable The resource to close. - * @param exceptionHandler Consumes exception thrown closing this resource. - * @since 2.10.0 - */ - public static void close(final AutoCloseable autoCloseable, final Consumer exceptionHandler) { - if (autoCloseable != null) { - try { - autoCloseable.close(); - } catch (final Exception e) { - if (exceptionHandler != null) { - exceptionHandler.accept(e); - } - } - } - } - - /** - * Closes the AutoCloseable (which may be null). - * - * @param autoCloseable an AutoCloseable, may be {@code null} - * @since 2.6.0 - */ - public static void closeQuietly(final AutoCloseable autoCloseable) { - close(autoCloseable, null); - } - - /** - * Closes the Connection (which may be null). - * - * @param connection a Connection, may be {@code null} - * @deprecated Use {@link #closeQuietly(AutoCloseable)}. - */ - @Deprecated - public static void closeQuietly(final Connection connection) { - closeQuietly((AutoCloseable) connection); - } - - /** - * Closes the ResultSet (which may be null). - * - * @param resultSet a ResultSet, may be {@code null} - * @deprecated Use {@link #closeQuietly(AutoCloseable)}. - */ - @Deprecated - public static void closeQuietly(final ResultSet resultSet) { - closeQuietly((AutoCloseable) resultSet); - } - - /** - * Closes the Statement (which may be null). - * - * @param statement a Statement, may be {@code null}. - * @deprecated Use {@link #closeQuietly(AutoCloseable)}. - */ - @Deprecated - public static void closeQuietly(final Statement statement) { - closeQuietly((AutoCloseable) statement); - } - - /** - * Gets a copy of SQL codes of fatal connection errors. - *
    - *
  • 57P01 (Admin shutdown)
  • - *
  • 57P02 (Crash shutdown)
  • - *
  • 57P03 (Cannot connect now)
  • - *
  • 01002 (SQL92 disconnect error)
  • - *
  • JZ0C0 (Sybase disconnect error)
  • - *
  • JZ0C1 (Sybase disconnect error)
  • - *
- * @return A copy SQL codes of fatal connection errors. - * @since 2.10.0 - */ - public static Set getDisconnectionSqlCodes() { - return new HashSet<>(DISCONNECTION_SQL_CODES); - } - - /** - * Gets the correct i18n message for the given key. - * - * @param key The key to look up an i18n message. - * @return The i18n message. - */ - public static String getMessage(final String key) { - return getMessage(key, (Object[]) null); - } - - /** - * Gets the correct i18n message for the given key with placeholders replaced by the supplied arguments. - * - * @param key A message key. - * @param args The message arguments. - * @return An i18n message. - */ - public static String getMessage(final String key, final Object... args) { - final String msg = messages.getString(key); - if (args == null || args.length == 0) { - return msg; - } - final MessageFormat mf = new MessageFormat(msg); - return mf.format(args, new StringBuffer(), null).toString(); - } - - /** - * Checks if the given SQL state corresponds to a fatal connection error. - * - * @param sqlState the SQL state to check. - * @return true if the SQL state is a fatal connection error, false otherwise. - * @since 2.13.0 - */ - static boolean isDisconnectionSqlCode(final String sqlState) { - return DISCONNECTION_SQL_CODES.contains(sqlState); - } - - static boolean isEmpty(final Collection collection) { - return collection == null || collection.isEmpty(); - } - - static boolean isSecurityEnabled() { - return System.getSecurityManager() != null; - } - - /** - * Converts the given String to a char[]. - * - * @param value may be null. - * @return a char[] or null. - */ - public static char[] toCharArray(final String value) { - return value != null ? value.toCharArray() : null; - } - - /** - * Converts the given char[] to a String. - * - * @param value may be null. - * @return a String or null. - */ - public static String toString(final char[] value) { - return value == null ? null : String.valueOf(value); - } - - /** - * Throws a LifetimeExceededException if the given pooled object's lifetime has exceeded a maximum duration. - * - * @param p The pooled object to test. - * @param maxDuration The maximum lifetime. - * @throws LifetimeExceededException Thrown if the given pooled object's lifetime has exceeded a maximum duration. - */ - public static void validateLifetime(final PooledObject p, final Duration maxDuration) throws LifetimeExceededException { - if (maxDuration.compareTo(Duration.ZERO) > 0) { - final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now()); - if (lifetimeDuration.compareTo(maxDuration) > 0) { - throw new LifetimeExceededException(getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxDuration)); - } - } - } - - private Utils() { - // not instantiable - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.text.MessageFormat; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.HashSet; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.pool2.PooledObject; + +/** + * Utility methods. + * + * @since 2.0 + */ +public final class Utils { + + private static final ResourceBundle messages = ResourceBundle + .getBundle(Utils.class.getPackage().getName() + ".LocalStrings"); + + /** + * Whether the security manager is enabled. + * + * @deprecated No replacement. + */ + @Deprecated + public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled(); + + /** + * Any SQL State starting with this value is considered a fatal disconnect. + */ + public static final String DISCONNECTION_SQL_CODE_PREFIX = "08"; + + /** + * SQL codes of fatal connection errors. + *
    + *
  • 57P01 (Admin shutdown)
  • + *
  • 57P02 (Crash shutdown)
  • + *
  • 57P03 (Cannot connect now)
  • + *
  • 01002 (SQL92 disconnect error)
  • + *
  • JZ0C0 (Sybase disconnect error)
  • + *
  • JZ0C1 (Sybase disconnect error)
  • + *
+ * @deprecated Use {@link #getDisconnectionSqlCodes()}. + */ + @Deprecated + public static final Set DISCONNECTION_SQL_CODES; + + static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {}; + + static final String[] EMPTY_STRING_ARRAY = {}; + static { + DISCONNECTION_SQL_CODES = new HashSet<>(); + DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown + DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown + DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now + DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error + DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error + DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error + } + + /** + * Checks for conflicts between two collections. + *

+ * If any overlap is found between the two provided collections, an {@link IllegalArgumentException} is thrown. + *

+ * + * @param codes1 The first collection of SQL state codes. + * @param codes2 The second collection of SQL state codes. + * @throws IllegalArgumentException if any codes overlap between the two collections. + * @since 2.13.0 + */ + static void checkSqlCodes(final Collection codes1, final Collection codes2) { + if (codes1 != null && codes2 != null) { + final Set test = new HashSet<>(codes1); + test.retainAll(codes2); + if (!test.isEmpty()) { + throw new IllegalArgumentException(test + " cannot be in both disconnectionSqlCodes and disconnectionIgnoreSqlCodes."); + } + } + } + + /** + * Clones the given char[] if not null. + * + * @param value may be null. + * @return a cloned char[] or null. + */ + public static char[] clone(final char[] value) { + return value == null ? null : value.clone(); + } + + /** + * Clones the given {@link Properties} without the standard "user" or "password" entries. + * + * @param properties may be null + * @return a clone of the input without the standard "user" or "password" entries. + * @since 2.8.0 + */ + public static Properties cloneWithoutCredentials(final Properties properties) { + if (properties != null) { + final Properties temp = (Properties) properties.clone(); + temp.remove(Constants.KEY_USER); + temp.remove(Constants.KEY_PASSWORD); + return temp; + } + return properties; + } + + /** + * Closes the given {@link AutoCloseable} and if an exception is caught, then calls {@code exceptionHandler}. + * + * @param autoCloseable The resource to close. + * @param exceptionHandler Consumes exception thrown closing this resource. + * @since 2.10.0 + */ + public static void close(final AutoCloseable autoCloseable, final Consumer exceptionHandler) { + if (autoCloseable != null) { + try { + autoCloseable.close(); + } catch (final Exception e) { + if (exceptionHandler != null) { + exceptionHandler.accept(e); + } + } + } + } + + /** + * Closes the AutoCloseable (which may be null). + * + * @param autoCloseable an AutoCloseable, may be {@code null} + * @since 2.6.0 + */ + public static void closeQuietly(final AutoCloseable autoCloseable) { + close(autoCloseable, null); + } + + /** + * Closes the Connection (which may be null). + * + * @param connection a Connection, may be {@code null} + * @deprecated Use {@link #closeQuietly(AutoCloseable)}. + */ + @Deprecated + public static void closeQuietly(final Connection connection) { + closeQuietly((AutoCloseable) connection); + } + + /** + * Closes the ResultSet (which may be null). + * + * @param resultSet a ResultSet, may be {@code null} + * @deprecated Use {@link #closeQuietly(AutoCloseable)}. + */ + @Deprecated + public static void closeQuietly(final ResultSet resultSet) { + closeQuietly((AutoCloseable) resultSet); + } + + /** + * Closes the Statement (which may be null). + * + * @param statement a Statement, may be {@code null}. + * @deprecated Use {@link #closeQuietly(AutoCloseable)}. + */ + @Deprecated + public static void closeQuietly(final Statement statement) { + closeQuietly((AutoCloseable) statement); + } + + /** + * Gets a copy of SQL codes of fatal connection errors. + *
    + *
  • 57P01 (Admin shutdown)
  • + *
  • 57P02 (Crash shutdown)
  • + *
  • 57P03 (Cannot connect now)
  • + *
  • 01002 (SQL92 disconnect error)
  • + *
  • JZ0C0 (Sybase disconnect error)
  • + *
  • JZ0C1 (Sybase disconnect error)
  • + *
+ * @return A copy SQL codes of fatal connection errors. + * @since 2.10.0 + */ + public static Set getDisconnectionSqlCodes() { + return new HashSet<>(DISCONNECTION_SQL_CODES); + } + + /** + * Gets the correct i18n message for the given key. + * + * @param key The key to look up an i18n message. + * @return The i18n message. + */ + public static String getMessage(final String key) { + return getMessage(key, (Object[]) null); + } + + /** + * Gets the correct i18n message for the given key with placeholders replaced by the supplied arguments. + * + * @param key A message key. + * @param args The message arguments. + * @return An i18n message. + */ + public static String getMessage(final String key, final Object... args) { + final String msg = messages.getString(key); + if (args == null || args.length == 0) { + return msg; + } + final MessageFormat mf = new MessageFormat(msg); + return mf.format(args, new StringBuffer(), null).toString(); + } + + /** + * Checks if the given SQL state corresponds to a fatal connection error. + * + * @param sqlState the SQL state to check. + * @return true if the SQL state is a fatal connection error, false otherwise. + * @since 2.13.0 + */ + static boolean isDisconnectionSqlCode(final String sqlState) { + return DISCONNECTION_SQL_CODES.contains(sqlState); + } + + static boolean isEmpty(final Collection collection) { + return collection == null || collection.isEmpty(); + } + + static boolean isSecurityEnabled() { + return System.getSecurityManager() != null; + } + + /** + * Converts the given String to a char[]. + * + * @param value may be null. + * @return a char[] or null. + */ + public static char[] toCharArray(final String value) { + return value != null ? value.toCharArray() : null; + } + + /** + * Converts the given char[] to a String. + * + * @param value may be null. + * @return a String or null. + */ + public static String toString(final char[] value) { + return value == null ? null : String.valueOf(value); + } + + /** + * Throws a LifetimeExceededException if the given pooled object's lifetime has exceeded a maximum duration. + * + * @param p The pooled object to test. + * @param maxDuration The maximum lifetime. + * @throws LifetimeExceededException Thrown if the given pooled object's lifetime has exceeded a maximum duration. + */ + public static void validateLifetime(final PooledObject p, final Duration maxDuration) throws LifetimeExceededException { + if (maxDuration.compareTo(Duration.ZERO) > 0) { + final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now()); + if (lifetimeDuration.compareTo(maxDuration) > 0) { + throw new LifetimeExceededException(getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxDuration)); + } + } + } + + private Utils() { + // not instantiable + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java index bbb8ecd66a..10adbfc8f4 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java @@ -1,316 +1,316 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.cpdsadapter; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import org.apache.commons.dbcp2.DelegatingCallableStatement; -import org.apache.commons.dbcp2.DelegatingConnection; -import org.apache.commons.dbcp2.DelegatingPreparedStatement; - -/** - * This class is the {@code Connection} that will be returned from - * {@code PooledConnectionImpl.getConnection()}. Most methods are wrappers around the JDBC 1.x - * {@code Connection}. A few exceptions include preparedStatement and close. In accordance with the JDBC - * specification this Connection cannot be used after closed() is called. Any further usage will result in an - * SQLException. - *

- * ConnectionImpl extends DelegatingConnection to enable access to the underlying connection. - *

- * - * @since 2.0 - */ -final class ConnectionImpl extends DelegatingConnection { - - private final boolean accessToUnderlyingConnectionAllowed; - - /** The object that instantiated this object */ - private final PooledConnectionImpl pooledConnection; - - /** - * Creates a {@code ConnectionImpl}. - * - * @param pooledConnection - * The PooledConnection that is calling the ctor. - * @param connection - * The JDBC 1.x Connection to wrap. - * @param accessToUnderlyingConnectionAllowed - * if true, then access is allowed to the underlying connection - */ - ConnectionImpl(final PooledConnectionImpl pooledConnection, final Connection connection, - final boolean accessToUnderlyingConnectionAllowed) { - super(connection); - this.pooledConnection = pooledConnection; - this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; - } - - /** - * Marks the Connection as closed, and notifies the pool that the pooled connection is available. - *

- * In accordance with the JDBC specification this Connection cannot be used after closed() is called. Any further - * usage will result in an SQLException. - *

- * - * @throws SQLException - * The database connection couldn't be closed. - */ - @Override - public void close() throws SQLException { - if (!isClosedInternal()) { - try { - passivate(); - } finally { - setClosedInternal(true); - pooledConnection.notifyListeners(); - } - } - } - - /** - * Gets the delegated connection, if allowed. - * - * @return the internal connection, or null if access is not allowed. - * @see #isAccessToUnderlyingConnectionAllowed() - */ - @Override - public Connection getDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return getDelegateInternal(); - } - return null; - } - - /** - * Gets the innermost connection, if allowed. - * - * @return the innermost internal connection, or null if access is not allowed. - * @see #isAccessToUnderlyingConnectionAllowed() - */ - @Override - public Connection getInnermostDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return super.getInnermostDelegateInternal(); - } - return null; - } - - /** - * Package-private for tests. - * - * @return the PooledConnectionImpl. - */ - PooledConnectionImpl getPooledConnectionImpl() { - return pooledConnection; - } - - /** - * If false, getDelegate() and getInnermostDelegate() will return null. - * - * @return true if access is allowed to the underlying connection - * @see ConnectionImpl - */ - public boolean isAccessToUnderlyingConnectionAllowed() { - return accessToUnderlyingConnectionAllowed; - } - - /** - * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may - * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. - * - * @param sql - * an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is - * specified using JDBC call escape syntax. - * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement. - * @throws SQLException - * Thrown if a database access error occurs or this method is called on a closed connection. - * @since 2.4.0 - */ - @Override - public CallableStatement prepareCall(final String sql) throws SQLException { - checkOpen(); - try { - return new DelegatingCallableStatement(this, pooledConnection.prepareCall(sql)); - } catch (final SQLException e) { - handleException(e); // Does not return - return null; - } - } - - /** - * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may - * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. - * - * @param sql - * a {@code String} object that is the SQL statement to be sent to the database; may contain on or - * more '?' parameters. - * @param resultSetType - * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce - * {@code ResultSet} objects with the given type and concurrency. - * @throws SQLException - * Thrown if a database access error occurs, this method is called on a closed connection or the given - * parameters are not {@code ResultSet} constants indicating type and concurrency. - * @since 2.4.0 - */ - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - checkOpen(); - try { - return new DelegatingCallableStatement(this, - pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency)); - } catch (final SQLException e) { - handleException(e); // Does not return - return null; - } - } - - /** - * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may - * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. - * - * @param sql - * a {@code String} object that is the SQL statement to be sent to the database; may contain on or - * more '?' parameters. - * @param resultSetType - * one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @param resultSetHoldability - * one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} - * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. - * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will - * generate {@code ResultSet} objects with the given type, concurrency, and holdability. - * @throws SQLException - * Thrown if a database access error occurs, this method is called on a closed connection or the given - * parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability. - * @since 2.4.0 - */ - @Override - public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - checkOpen(); - try { - return new DelegatingCallableStatement(this, - pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); - } catch (final SQLException e) { - handleException(e); // Does not return - return null; - } - } - - /** - * If pooling of {@code PreparedStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may - * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. - * - * @param sql - * SQL statement to be prepared - * @return the prepared statement - * @throws SQLException - * if this connection is closed or an error occurs in the wrapped connection. - */ - @Override - public PreparedStatement prepareStatement(final String sql) throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql)); - } catch (final SQLException e) { - handleException(e); // Does not return - return null; - } - } - - @Override - public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, autoGeneratedKeys)); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - // - // Methods for accessing the delegate connection - // - - /** - * If pooling of {@code PreparedStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may - * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. - * - * @throws SQLException - * if this connection is closed or an error occurs in the wrapped connection. - */ - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, - pooledConnection.prepareStatement(sql, resultSetType, resultSetConcurrency)); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, - pooledConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, columnIndexes)); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - - @Override - public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { - checkOpen(); - try { - return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, columnNames)); - } catch (final SQLException e) { - handleException(e); - return null; - } - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.cpdsadapter; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.apache.commons.dbcp2.DelegatingCallableStatement; +import org.apache.commons.dbcp2.DelegatingConnection; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; + +/** + * This class is the {@code Connection} that will be returned from + * {@code PooledConnectionImpl.getConnection()}. Most methods are wrappers around the JDBC 1.x + * {@code Connection}. A few exceptions include preparedStatement and close. In accordance with the JDBC + * specification this Connection cannot be used after closed() is called. Any further usage will result in an + * SQLException. + *

+ * ConnectionImpl extends DelegatingConnection to enable access to the underlying connection. + *

+ * + * @since 2.0 + */ +final class ConnectionImpl extends DelegatingConnection { + + private final boolean accessToUnderlyingConnectionAllowed; + + /** The object that instantiated this object */ + private final PooledConnectionImpl pooledConnection; + + /** + * Creates a {@code ConnectionImpl}. + * + * @param pooledConnection + * The PooledConnection that is calling the ctor. + * @param connection + * The JDBC 1.x Connection to wrap. + * @param accessToUnderlyingConnectionAllowed + * if true, then access is allowed to the underlying connection + */ + ConnectionImpl(final PooledConnectionImpl pooledConnection, final Connection connection, + final boolean accessToUnderlyingConnectionAllowed) { + super(connection); + this.pooledConnection = pooledConnection; + this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; + } + + /** + * Marks the Connection as closed, and notifies the pool that the pooled connection is available. + *

+ * In accordance with the JDBC specification this Connection cannot be used after closed() is called. Any further + * usage will result in an SQLException. + *

+ * + * @throws SQLException + * The database connection couldn't be closed. + */ + @Override + public void close() throws SQLException { + if (!isClosedInternal()) { + try { + passivate(); + } finally { + setClosedInternal(true); + pooledConnection.notifyListeners(); + } + } + } + + /** + * Gets the delegated connection, if allowed. + * + * @return the internal connection, or null if access is not allowed. + * @see #isAccessToUnderlyingConnectionAllowed() + */ + @Override + public Connection getDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return getDelegateInternal(); + } + return null; + } + + /** + * Gets the innermost connection, if allowed. + * + * @return the innermost internal connection, or null if access is not allowed. + * @see #isAccessToUnderlyingConnectionAllowed() + */ + @Override + public Connection getInnermostDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return super.getInnermostDelegateInternal(); + } + return null; + } + + /** + * Package-private for tests. + * + * @return the PooledConnectionImpl. + */ + PooledConnectionImpl getPooledConnectionImpl() { + return pooledConnection; + } + + /** + * If false, getDelegate() and getInnermostDelegate() will return null. + * + * @return true if access is allowed to the underlying connection + * @see ConnectionImpl + */ + public boolean isAccessToUnderlyingConnectionAllowed() { + return accessToUnderlyingConnectionAllowed; + } + + /** + * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is + * specified using JDBC call escape syntax. + * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement. + * @throws SQLException + * Thrown if a database access error occurs or this method is called on a closed connection. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(final String sql) throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, pooledConnection.prepareCall(sql)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** + * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * a {@code String} object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce + * {@code ResultSet} objects with the given type and concurrency. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not {@code ResultSet} constants indicating type and concurrency. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, + pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** + * If pooling of {@code CallableStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * a {@code String} object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @param resultSetHoldability + * one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} + * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. + * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will + * generate {@code ResultSet} objects with the given type, concurrency, and holdability. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, + pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** + * If pooling of {@code PreparedStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * SQL statement to be prepared + * @return the prepared statement + * @throws SQLException + * if this connection is closed or an error occurs in the wrapped connection. + */ + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, autoGeneratedKeys)); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + // + // Methods for accessing the delegate connection + // + + /** + * If pooling of {@code PreparedStatement}s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @throws SQLException + * if this connection is closed or an error occurs in the wrapped connection. + */ + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, + pooledConnection.prepareStatement(sql, resultSetType, resultSetConcurrency)); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, + pooledConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, columnIndexes)); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + + @Override + public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + checkOpen(); + try { + return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, columnNames)); + } catch (final SQLException e) { + handleException(e); + return null; + } + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java index 6281592127..b5cc18a96f 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java @@ -1,839 +1,839 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.cpdsadapter; - -import java.io.PrintWriter; -import java.io.Serializable; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.time.Duration; -import java.util.Hashtable; -import java.util.Properties; -import java.util.logging.Logger; - -import javax.naming.Context; -import javax.naming.Name; -import javax.naming.NamingException; -import javax.naming.RefAddr; -import javax.naming.Reference; -import javax.naming.Referenceable; -import javax.naming.StringRefAddr; -import javax.naming.spi.ObjectFactory; -import javax.sql.ConnectionPoolDataSource; -import javax.sql.PooledConnection; - -import org.apache.commons.dbcp2.Constants; -import org.apache.commons.dbcp2.DelegatingPreparedStatement; -import org.apache.commons.dbcp2.PStmtKey; -import org.apache.commons.dbcp2.Utils; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.impl.BaseObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericKeyedObjectPool; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; - -/** - *

- * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but - * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used - * within general applications. They are used by {@code DataSource} implementations that pool - * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container - * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are - * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical - * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection. - *

- *

- * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any - * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the - * {@code ConnectionPoolDataSource} with or without the use of JNDI. - *

- *

- * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2 - * {@code ConnectionPoolDataSource} implementation, but is addressed within the JDBC 3 specification. The - * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the DBCP package for some time, but it has not - * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled - * with the poolPreparedStatements attribute. - *

- *

- * The package documentation contains an example using Apache Catalina and JNDI. The - * datasources package documentation shows how to use - * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI. - *

- * - * @since 2.0 - */ -public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory { - - private static final String KEY_MIN_EVICTABLE_IDLE_DURATION = "minEvictableIdleDuration"; - - private static final String KEY_DURATION_BETWEEN_EVICTION_RUNS = "durationBetweenEvictionRuns"; - - private static final String KEY_LOGIN_TIMEOUT = "loginTimeout"; - - private static final String KEY_URL = "url"; - - private static final String KEY_DRIVER = "driver"; - - private static final String KEY_DESCRIPTION = "description"; - - private static final String KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed"; - - private static final String KEY_MAX_PREPARED_STATEMENTS = "maxPreparedStatements"; - - private static final String KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; - - private static final String KEY_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; - - private static final String KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis"; - - private static final String KEY_MAX_IDLE = "maxIdle"; - - private static final String KEY_POOL_PREPARED_STATEMENTS = "poolPreparedStatements"; - - private static final long serialVersionUID = -4820523787212147844L; - - private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, further initialization is not allowed."; - - static { - // Attempt to prevent deadlocks - see DBCP-272 - DriverManager.getDrivers(); - } - - /** Description */ - private String description; - - /** Connection string */ - private String connectionString; - - /** User name */ - private String userName; - - /** User password */ - private char[] userPassword; - - /** Driver class name */ - private String driver; - - /** Login TimeOut in seconds */ - private int loginTimeout; - - /** Log stream. NOT USED */ - private transient PrintWriter logWriter; - - // PreparedStatement pool properties - private boolean poolPreparedStatements; - private int maxIdle = 10; - private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; - private int numTestsPerEvictionRun = -1; - private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; - - private int maxPreparedStatements = -1; - - /** Whether or not getConnection has been called */ - private volatile boolean getConnectionCalled; - - /** Connection properties passed to JDBC Driver */ - private Properties connectionProperties; - - /** - * Controls access to the underlying connection - */ - private boolean accessToUnderlyingConnectionAllowed; - - /** - * Default no-argument constructor for Serialization - */ - public DriverAdapterCPDS() { - } - - /** - * Throws an IllegalStateException, if a PooledConnection has already been requested. - */ - private void assertInitializationAllowed() throws IllegalStateException { - if (getConnectionCalled) { - throw new IllegalStateException(GET_CONNECTION_CALLED); - } - } - - private boolean getBooleanContentString(final RefAddr ra) { - return Boolean.parseBoolean(getStringContent(ra)); - } - - /** - * Gets the connection properties passed to the JDBC driver. - * - * @return the JDBC connection properties used when creating connections. - */ - public Properties getConnectionProperties() { - return connectionProperties; - } - - /** - * Gets the value of description. This property is here for use by the code which will deploy this data source. It - * is not used internally. - * - * @return value of description, may be null. - * @see #setDescription(String) - */ - public String getDescription() { - return description; - } - - /** - * Gets the driver class name. - * - * @return value of driver. - */ - public String getDriver() { - return driver; - } - - /** - * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no - * idle object evictor thread will be run. - * - * @return the value of the evictor thread timer - * @see #setDurationBetweenEvictionRuns(Duration) - * @since 2.9.0 - */ - public Duration getDurationBetweenEvictionRuns() { - return durationBetweenEvictionRuns; - } - - private int getIntegerStringContent(final RefAddr ra) { - return Integer.parseInt(getStringContent(ra)); - } - - /** - * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT - * USED. - */ - @Override - public int getLoginTimeout() { - return loginTimeout; - } - - /** - * Gets the log writer for this data source. NOT USED. - */ - @Override - public PrintWriter getLogWriter() { - return logWriter; - } - - /** - * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or - * negative for no limit. - * - * @return the value of maxIdle - */ - public int getMaxIdle() { - return maxIdle; - } - - /** - * Gets the maximum number of prepared statements. - * - * @return maxPrepartedStatements value - */ - public int getMaxPreparedStatements() { - return maxPreparedStatements; - } - - /** - * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the - * idle object evictor (if any). - * - * @see #setMinEvictableIdleDuration - * @see #setDurationBetweenEvictionRuns - * @return the minimum amount of time a statement may sit idle in the pool. - * @since 2.9.0 - */ - public Duration getMinEvictableIdleDuration() { - return minEvictableIdleDuration; - } - - /** - * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the - * idle object evictor (if any). - * - * @see #setMinEvictableIdleTimeMillis - * @see #setTimeBetweenEvictionRunsMillis - * @return the minimum amount of time a statement may sit idle in the pool. - * @deprecated USe {@link #getMinEvictableIdleDuration()}. - */ - @Deprecated - public int getMinEvictableIdleTimeMillis() { - return (int) minEvictableIdleDuration.toMillis(); - } - - /** - * Gets the number of statements to examine during each run of the idle object evictor thread (if any.) - * - * @see #setNumTestsPerEvictionRun - * @see #setTimeBetweenEvictionRunsMillis - * @return the number of statements to examine during each run of the idle object evictor thread (if any.) - */ - public int getNumTestsPerEvictionRun() { - return numTestsPerEvictionRun; - } - - /** - * Implements {@link ObjectFactory} to create an instance of this class - */ - @Override - public Object getObjectInstance(final Object refObj, final Name name, final Context context, final Hashtable env) throws ClassNotFoundException { - // The spec says to return null if we can't create an instance - // of the reference - DriverAdapterCPDS cpds = null; - if (refObj instanceof Reference) { - final Reference ref = (Reference) refObj; - if (ref.getClassName().equals(getClass().getName())) { - RefAddr ra = ref.get(KEY_DESCRIPTION); - if (isNotEmpty(ra)) { - setDescription(getStringContent(ra)); - } - - ra = ref.get(KEY_DRIVER); - if (isNotEmpty(ra)) { - setDriver(getStringContent(ra)); - } - ra = ref.get(KEY_URL); - if (isNotEmpty(ra)) { - setUrl(getStringContent(ra)); - } - ra = ref.get(Constants.KEY_USER); - if (isNotEmpty(ra)) { - setUser(getStringContent(ra)); - } - ra = ref.get(Constants.KEY_PASSWORD); - if (isNotEmpty(ra)) { - setPassword(getStringContent(ra)); - } - - ra = ref.get(KEY_POOL_PREPARED_STATEMENTS); - if (isNotEmpty(ra)) { - setPoolPreparedStatements(getBooleanContentString(ra)); - } - ra = ref.get(KEY_MAX_IDLE); - if (isNotEmpty(ra)) { - setMaxIdle(getIntegerStringContent(ra)); - } - - ra = ref.get(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS); - if (isNotEmpty(ra)) { - setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra)); - } - - ra = ref.get(KEY_NUM_TESTS_PER_EVICTION_RUN); - if (isNotEmpty(ra)) { - setNumTestsPerEvictionRun(getIntegerStringContent(ra)); - } - - ra = ref.get(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS); - if (isNotEmpty(ra)) { - setMinEvictableIdleTimeMillis(getIntegerStringContent(ra)); - } - - ra = ref.get(KEY_MAX_PREPARED_STATEMENTS); - if (isNotEmpty(ra)) { - setMaxPreparedStatements(getIntegerStringContent(ra)); - } - - ra = ref.get(KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED); - if (isNotEmpty(ra)) { - setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra)); - } - - cpds = this; - } - } - return cpds; - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(); - } - - /** - * Gets the value of password for the default user. - * - * @return value of password. - */ - public String getPassword() { - return Utils.toString(userPassword); - } - - /** - * Gets the value of password for the default user. - * - * @return value of password. - * @since 2.4.0 - */ - public char[] getPasswordCharArray() { - return Utils.clone(userPassword); - } - - /** - * Attempts to establish a database connection using the default user and password. - */ - @Override - public PooledConnection getPooledConnection() throws SQLException { - return getPooledConnection(getUser(), getPassword()); - } - - /** - * Attempts to establish a database connection. - * - * @param pooledUserName name to be used for the connection - * @param pooledUserPassword password to be used fur the connection - */ - @Override - public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword) throws SQLException { - getConnectionCalled = true; - if (connectionProperties != null) { - update(connectionProperties, Constants.KEY_USER, pooledUserName); - update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword); - } - // Workaround for buggy WebLogic 5.1 class loader - ignore ClassCircularityError upon first invocation. - PooledConnectionImpl pooledConnection = null; - try { - pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword); - } catch (final ClassCircularityError e) { - pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword); - } - if (isPoolPreparedStatements()) { - final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); - config.setMaxTotalPerKey(Integer.MAX_VALUE); - config.setBlockWhenExhausted(false); - config.setMaxWait(Duration.ZERO); - config.setMaxIdlePerKey(getMaxIdle()); - if (getMaxPreparedStatements() <= 0) { - // Since there is no limit, create a prepared statement pool with an eviction thread; - // evictor settings are the same as the connection pool settings. - config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns()); - config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun()); - config.setMinEvictableIdleDuration(getMinEvictableIdleDuration()); - } else { - // Since there is a limit, create a prepared statement pool without an eviction thread; - // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared. - // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method - config.setMaxTotal(getMaxPreparedStatements()); - config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1)); - config.setNumTestsPerEvictionRun(0); - config.setMinEvictableIdleDuration(Duration.ZERO); - } - @SuppressWarnings("resource") // PooledConnectionImpl closes - final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config); - pooledConnection.setStatementPool(stmtPool); - } - return pooledConnection; - } - - @SuppressWarnings("resource") // Caller closes - private PooledConnectionImpl getPooledConnectionImpl(final String pooledUserName, final String pooledUserPassword) throws SQLException { - PooledConnectionImpl pooledConnection; - if (connectionProperties != null) { - pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), connectionProperties)); - } else { - pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword)); - } - pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); - return pooledConnection; - } - - /** - * Implements {@link Referenceable}. - */ - @Override - public Reference getReference() throws NamingException { - // this class implements its own factory - final String factory = getClass().getName(); - - final Reference ref = new Reference(getClass().getName(), factory, null); - - ref.add(new StringRefAddr(KEY_DESCRIPTION, getDescription())); - ref.add(new StringRefAddr(KEY_DRIVER, getDriver())); - ref.add(new StringRefAddr(KEY_LOGIN_TIMEOUT, String.valueOf(getLoginTimeout()))); - ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword())); - ref.add(new StringRefAddr(Constants.KEY_USER, getUser())); - ref.add(new StringRefAddr(KEY_URL, getUrl())); - - ref.add(new StringRefAddr(KEY_POOL_PREPARED_STATEMENTS, String.valueOf(isPoolPreparedStatements()))); - ref.add(new StringRefAddr(KEY_MAX_IDLE, String.valueOf(getMaxIdle()))); - ref.add(new StringRefAddr(KEY_NUM_TESTS_PER_EVICTION_RUN, String.valueOf(getNumTestsPerEvictionRun()))); - ref.add(new StringRefAddr(KEY_MAX_PREPARED_STATEMENTS, String.valueOf(getMaxPreparedStatements()))); - // - // Pair of current and deprecated. - ref.add(new StringRefAddr(KEY_DURATION_BETWEEN_EVICTION_RUNS, String.valueOf(getDurationBetweenEvictionRuns()))); - ref.add(new StringRefAddr(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS, String.valueOf(getTimeBetweenEvictionRunsMillis()))); - // - // Pair of current and deprecated. - ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_DURATION, String.valueOf(getMinEvictableIdleDuration()))); - ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(getMinEvictableIdleTimeMillis()))); - - return ref; - } - - private String getStringContent(final RefAddr ra) { - return ra.getContent().toString(); - } - - /** - * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no - * idle object evictor thread will be run. - * - * @return the value of the evictor thread timer - * @see #setDurationBetweenEvictionRuns(Duration) - * @deprecated Use {@link #getDurationBetweenEvictionRuns()}. - */ - @Deprecated - public long getTimeBetweenEvictionRunsMillis() { - return durationBetweenEvictionRuns.toMillis(); - } - - /** - * Gets the value of connection string used to locate the database for this data source. - * - * @return value of connection string. - */ - public String getUrl() { - return connectionString; - } - - /** - * Gets the value of default user (login or user name). - * - * @return value of user. - */ - public String getUser() { - return userName; - } - - /** - * Returns the value of the accessToUnderlyingConnectionAllowed property. - * - * @return true if access to the underlying is allowed, false otherwise. - */ - public synchronized boolean isAccessToUnderlyingConnectionAllowed() { - return this.accessToUnderlyingConnectionAllowed; - } - - private boolean isNotEmpty(final RefAddr ra) { - return ra != null && ra.getContent() != null; - } - - /** - * Whether to toggle the pooling of {@code PreparedStatement}s - * - * @return value of poolPreparedStatements. - */ - public boolean isPoolPreparedStatements() { - return poolPreparedStatements; - } - - /** - * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to - * the underlying connection. (Default: false) - * - * @param allow Access to the underlying connection is granted when true. - */ - public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { - this.accessToUnderlyingConnectionAllowed = allow; - } - - /** - * Sets the connection properties passed to the JDBC driver. - *

- * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are - * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()} - * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when - * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or - * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not - * null. - *

- * - * @param props Connection properties to use when creating new connections. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setConnectionProperties(final Properties props) { - assertInitializationAllowed(); - connectionProperties = props; - if (connectionProperties != null) { - final String user = connectionProperties.getProperty(Constants.KEY_USER); - if (user != null) { - setUser(user); - } - final String password = connectionProperties.getProperty(Constants.KEY_PASSWORD); - if (password != null) { - setPassword(password); - } - } - } - - /** - * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is - * not used internally. - * - * @param description Value to assign to description. - */ - public void setDescription(final String description) { - this.description = description; - } - - /** - * Sets the driver class name. Setting the driver class name cause the driver to be registered with the - * DriverManager. - * - * @param driver Value to assign to driver. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - * @throws ClassNotFoundException if the class cannot be located - */ - public void setDriver(final String driver) throws ClassNotFoundException { - assertInitializationAllowed(); - this.driver = driver; - // make sure driver is registered - Class.forName(driver); - } - - /** - * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no - * idle object evictor thread will be run. - * - * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor - * thread. When non-positive, no idle object evictor thread will be run. - * @see #getDurationBetweenEvictionRuns() - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - * @since 2.9.0 - */ - public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) { - assertInitializationAllowed(); - this.durationBetweenEvictionRuns = durationBetweenEvictionRuns; - } - - /** - * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT - * USED. - */ - @Override - public void setLoginTimeout(final int seconds) { - this.loginTimeout = seconds; - } - - /** - * Sets the log writer for this data source. NOT USED. - */ - @Override - public void setLogWriter(final PrintWriter logWriter) { - this.logWriter = logWriter; - } - - /** - * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or - * negative for no limit. - * - * @param maxIdle The maximum number of statements that can remain idle - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setMaxIdle(final int maxIdle) { - assertInitializationAllowed(); - this.maxIdle = maxIdle; - } - - /** - * Sets the maximum number of prepared statements. - * - * @param maxPreparedStatements the new maximum number of prepared statements - */ - public void setMaxPreparedStatements(final int maxPreparedStatements) { - this.maxPreparedStatements = maxPreparedStatements; - } - - /** - * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the - * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone. - * - * @param minEvictableIdleDuration minimum time to set in milliseconds. - * @see #getMinEvictableIdleDuration() - * @see #setDurationBetweenEvictionRuns(Duration) - * @throws IllegalStateException if {@link #getPooledConnection()} has been called. - * @since 2.9.0 - */ - public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) { - assertInitializationAllowed(); - this.minEvictableIdleDuration = minEvictableIdleDuration; - } - - /** - * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the - * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone. - * - * @param minEvictableIdleTimeMillis minimum time to set in milliseconds. - * @see #getMinEvictableIdleDuration() - * @see #setDurationBetweenEvictionRuns(Duration) - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}. - */ - @Deprecated - public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) { - assertInitializationAllowed(); - this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis); - } - - /** - * Sets the number of statements to examine during each run of the idle object evictor thread (if any). - *

- * When a negative value is supplied, - * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run. - * I.e., when the value is -n, roughly one nth of the idle objects will be tested per run. - *

- * - * @param numTestsPerEvictionRun number of statements to examine per run - * @see #getNumTestsPerEvictionRun() - * @see #setDurationBetweenEvictionRuns(Duration) - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { - assertInitializationAllowed(); - this.numTestsPerEvictionRun = numTestsPerEvictionRun; - } - - /** - * Sets the value of password for the default user. - * - * @param userPassword Value to assign to password. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setPassword(final char[] userPassword) { - assertInitializationAllowed(); - this.userPassword = Utils.clone(userPassword); - update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword)); - } - - /** - * Sets the value of password for the default user. - * - * @param userPassword Value to assign to password. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setPassword(final String userPassword) { - assertInitializationAllowed(); - this.userPassword = Utils.toCharArray(userPassword); - update(connectionProperties, Constants.KEY_PASSWORD, userPassword); - } - - /** - * Whether to toggle the pooling of {@code PreparedStatement}s - * - * @param poolPreparedStatements true to pool statements. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setPoolPreparedStatements(final boolean poolPreparedStatements) { - assertInitializationAllowed(); - this.poolPreparedStatements = poolPreparedStatements; - } - - /** - * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no - * idle object evictor thread will be run. - * - * @param timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle object evictor - * thread. When non-positive, no idle object evictor thread will be run. - * @see #getDurationBetweenEvictionRuns() - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}. - */ - @Deprecated - public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { - assertInitializationAllowed(); - this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis); - } - - /** - * Sets the value of URL string used to locate the database for this data source. - * - * @param connectionString Value to assign to connection string. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setUrl(final String connectionString) { - assertInitializationAllowed(); - this.connectionString = connectionString; - } - - /** - * Sets the value of default user (login or user name). - * - * @param userName Value to assign to user. - * @throws IllegalStateException if {@link #getPooledConnection()} has been called - */ - public void setUser(final String userName) { - assertInitializationAllowed(); - this.userName = userName; - update(connectionProperties, Constants.KEY_USER, userName); - } - - /** - * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties. - * - * @since 2.6.0 - */ - @Override - public synchronized String toString() { - final StringBuilder builder = new StringBuilder(super.toString()); - builder.append("[description="); - builder.append(description); - builder.append(", connectionString="); - // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string - // is not in a legal URL format? - builder.append(connectionString); - builder.append(", driver="); - builder.append(driver); - builder.append(", loginTimeout="); - builder.append(loginTimeout); - builder.append(", poolPreparedStatements="); - builder.append(poolPreparedStatements); - builder.append(", maxIdle="); - builder.append(maxIdle); - builder.append(", timeBetweenEvictionRunsMillis="); - builder.append(durationBetweenEvictionRuns); - builder.append(", numTestsPerEvictionRun="); - builder.append(numTestsPerEvictionRun); - builder.append(", minEvictableIdleTimeMillis="); - builder.append(minEvictableIdleDuration); - builder.append(", maxPreparedStatements="); - builder.append(maxPreparedStatements); - builder.append(", getConnectionCalled="); - builder.append(getConnectionCalled); - builder.append(", connectionProperties="); - builder.append(Utils.cloneWithoutCredentials(connectionProperties)); - builder.append(", accessToUnderlyingConnectionAllowed="); - builder.append(accessToUnderlyingConnectionAllowed); - builder.append("]"); - return builder.toString(); - } - - private void update(final Properties properties, final String key, final String value) { - if (properties != null && key != null) { - if (value == null) { - properties.remove(key); - } else { - properties.setProperty(key, value); - } - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.cpdsadapter; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.logging.Logger; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.PooledConnection; + +import org.apache.commons.dbcp2.Constants; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.PStmtKey; +import org.apache.commons.dbcp2.Utils; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + *

+ * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but + * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used + * within general applications. They are used by {@code DataSource} implementations that pool + * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container + * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are + * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical + * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection. + *

+ *

+ * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any + * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the + * {@code ConnectionPoolDataSource} with or without the use of JNDI. + *

+ *

+ * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2 + * {@code ConnectionPoolDataSource} implementation, but is addressed within the JDBC 3 specification. The + * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the DBCP package for some time, but it has not + * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled + * with the poolPreparedStatements attribute. + *

+ *

+ * The package documentation contains an example using Apache Catalina and JNDI. The + * datasources package documentation shows how to use + * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI. + *

+ * + * @since 2.0 + */ +public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory { + + private static final String KEY_MIN_EVICTABLE_IDLE_DURATION = "minEvictableIdleDuration"; + + private static final String KEY_DURATION_BETWEEN_EVICTION_RUNS = "durationBetweenEvictionRuns"; + + private static final String KEY_LOGIN_TIMEOUT = "loginTimeout"; + + private static final String KEY_URL = "url"; + + private static final String KEY_DRIVER = "driver"; + + private static final String KEY_DESCRIPTION = "description"; + + private static final String KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed"; + + private static final String KEY_MAX_PREPARED_STATEMENTS = "maxPreparedStatements"; + + private static final String KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; + + private static final String KEY_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; + + private static final String KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis"; + + private static final String KEY_MAX_IDLE = "maxIdle"; + + private static final String KEY_POOL_PREPARED_STATEMENTS = "poolPreparedStatements"; + + private static final long serialVersionUID = -4820523787212147844L; + + private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, further initialization is not allowed."; + + static { + // Attempt to prevent deadlocks - see DBCP-272 + DriverManager.getDrivers(); + } + + /** Description */ + private String description; + + /** Connection string */ + private String connectionString; + + /** User name */ + private String userName; + + /** User password */ + private char[] userPassword; + + /** Driver class name */ + private String driver; + + /** Login TimeOut in seconds */ + private int loginTimeout; + + /** Log stream. NOT USED */ + private transient PrintWriter logWriter; + + // PreparedStatement pool properties + private boolean poolPreparedStatements; + private int maxIdle = 10; + private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; + private int numTestsPerEvictionRun = -1; + private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; + + private int maxPreparedStatements = -1; + + /** Whether or not getConnection has been called */ + private volatile boolean getConnectionCalled; + + /** Connection properties passed to JDBC Driver */ + private Properties connectionProperties; + + /** + * Controls access to the underlying connection + */ + private boolean accessToUnderlyingConnectionAllowed; + + /** + * Default no-argument constructor for Serialization + */ + public DriverAdapterCPDS() { + } + + /** + * Throws an IllegalStateException, if a PooledConnection has already been requested. + */ + private void assertInitializationAllowed() throws IllegalStateException { + if (getConnectionCalled) { + throw new IllegalStateException(GET_CONNECTION_CALLED); + } + } + + private boolean getBooleanContentString(final RefAddr ra) { + return Boolean.parseBoolean(getStringContent(ra)); + } + + /** + * Gets the connection properties passed to the JDBC driver. + * + * @return the JDBC connection properties used when creating connections. + */ + public Properties getConnectionProperties() { + return connectionProperties; + } + + /** + * Gets the value of description. This property is here for use by the code which will deploy this data source. It + * is not used internally. + * + * @return value of description, may be null. + * @see #setDescription(String) + */ + public String getDescription() { + return description; + } + + /** + * Gets the driver class name. + * + * @return value of driver. + */ + public String getDriver() { + return driver; + } + + /** + * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no + * idle object evictor thread will be run. + * + * @return the value of the evictor thread timer + * @see #setDurationBetweenEvictionRuns(Duration) + * @since 2.9.0 + */ + public Duration getDurationBetweenEvictionRuns() { + return durationBetweenEvictionRuns; + } + + private int getIntegerStringContent(final RefAddr ra) { + return Integer.parseInt(getStringContent(ra)); + } + + /** + * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT + * USED. + */ + @Override + public int getLoginTimeout() { + return loginTimeout; + } + + /** + * Gets the log writer for this data source. NOT USED. + */ + @Override + public PrintWriter getLogWriter() { + return logWriter; + } + + /** + * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or + * negative for no limit. + * + * @return the value of maxIdle + */ + public int getMaxIdle() { + return maxIdle; + } + + /** + * Gets the maximum number of prepared statements. + * + * @return maxPrepartedStatements value + */ + public int getMaxPreparedStatements() { + return maxPreparedStatements; + } + + /** + * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the + * idle object evictor (if any). + * + * @see #setMinEvictableIdleDuration + * @see #setDurationBetweenEvictionRuns + * @return the minimum amount of time a statement may sit idle in the pool. + * @since 2.9.0 + */ + public Duration getMinEvictableIdleDuration() { + return minEvictableIdleDuration; + } + + /** + * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the + * idle object evictor (if any). + * + * @see #setMinEvictableIdleTimeMillis + * @see #setTimeBetweenEvictionRunsMillis + * @return the minimum amount of time a statement may sit idle in the pool. + * @deprecated USe {@link #getMinEvictableIdleDuration()}. + */ + @Deprecated + public int getMinEvictableIdleTimeMillis() { + return (int) minEvictableIdleDuration.toMillis(); + } + + /** + * Gets the number of statements to examine during each run of the idle object evictor thread (if any.) + * + * @see #setNumTestsPerEvictionRun + * @see #setTimeBetweenEvictionRunsMillis + * @return the number of statements to examine during each run of the idle object evictor thread (if any.) + */ + public int getNumTestsPerEvictionRun() { + return numTestsPerEvictionRun; + } + + /** + * Implements {@link ObjectFactory} to create an instance of this class + */ + @Override + public Object getObjectInstance(final Object refObj, final Name name, final Context context, final Hashtable env) throws ClassNotFoundException { + // The spec says to return null if we can't create an instance + // of the reference + DriverAdapterCPDS cpds = null; + if (refObj instanceof Reference) { + final Reference ref = (Reference) refObj; + if (ref.getClassName().equals(getClass().getName())) { + RefAddr ra = ref.get(KEY_DESCRIPTION); + if (isNotEmpty(ra)) { + setDescription(getStringContent(ra)); + } + + ra = ref.get(KEY_DRIVER); + if (isNotEmpty(ra)) { + setDriver(getStringContent(ra)); + } + ra = ref.get(KEY_URL); + if (isNotEmpty(ra)) { + setUrl(getStringContent(ra)); + } + ra = ref.get(Constants.KEY_USER); + if (isNotEmpty(ra)) { + setUser(getStringContent(ra)); + } + ra = ref.get(Constants.KEY_PASSWORD); + if (isNotEmpty(ra)) { + setPassword(getStringContent(ra)); + } + + ra = ref.get(KEY_POOL_PREPARED_STATEMENTS); + if (isNotEmpty(ra)) { + setPoolPreparedStatements(getBooleanContentString(ra)); + } + ra = ref.get(KEY_MAX_IDLE); + if (isNotEmpty(ra)) { + setMaxIdle(getIntegerStringContent(ra)); + } + + ra = ref.get(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS); + if (isNotEmpty(ra)) { + setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra)); + } + + ra = ref.get(KEY_NUM_TESTS_PER_EVICTION_RUN); + if (isNotEmpty(ra)) { + setNumTestsPerEvictionRun(getIntegerStringContent(ra)); + } + + ra = ref.get(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS); + if (isNotEmpty(ra)) { + setMinEvictableIdleTimeMillis(getIntegerStringContent(ra)); + } + + ra = ref.get(KEY_MAX_PREPARED_STATEMENTS); + if (isNotEmpty(ra)) { + setMaxPreparedStatements(getIntegerStringContent(ra)); + } + + ra = ref.get(KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED); + if (isNotEmpty(ra)) { + setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra)); + } + + cpds = this; + } + } + return cpds; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Gets the value of password for the default user. + * + * @return value of password. + */ + public String getPassword() { + return Utils.toString(userPassword); + } + + /** + * Gets the value of password for the default user. + * + * @return value of password. + * @since 2.4.0 + */ + public char[] getPasswordCharArray() { + return Utils.clone(userPassword); + } + + /** + * Attempts to establish a database connection using the default user and password. + */ + @Override + public PooledConnection getPooledConnection() throws SQLException { + return getPooledConnection(getUser(), getPassword()); + } + + /** + * Attempts to establish a database connection. + * + * @param pooledUserName name to be used for the connection + * @param pooledUserPassword password to be used fur the connection + */ + @Override + public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword) throws SQLException { + getConnectionCalled = true; + if (connectionProperties != null) { + update(connectionProperties, Constants.KEY_USER, pooledUserName); + update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword); + } + // Workaround for buggy WebLogic 5.1 class loader - ignore ClassCircularityError upon first invocation. + PooledConnectionImpl pooledConnection = null; + try { + pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword); + } catch (final ClassCircularityError e) { + pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword); + } + if (isPoolPreparedStatements()) { + final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); + config.setMaxTotalPerKey(Integer.MAX_VALUE); + config.setBlockWhenExhausted(false); + config.setMaxWait(Duration.ZERO); + config.setMaxIdlePerKey(getMaxIdle()); + if (getMaxPreparedStatements() <= 0) { + // Since there is no limit, create a prepared statement pool with an eviction thread; + // evictor settings are the same as the connection pool settings. + config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns()); + config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun()); + config.setMinEvictableIdleDuration(getMinEvictableIdleDuration()); + } else { + // Since there is a limit, create a prepared statement pool without an eviction thread; + // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared. + // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method + config.setMaxTotal(getMaxPreparedStatements()); + config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1)); + config.setNumTestsPerEvictionRun(0); + config.setMinEvictableIdleDuration(Duration.ZERO); + } + @SuppressWarnings("resource") // PooledConnectionImpl closes + final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config); + pooledConnection.setStatementPool(stmtPool); + } + return pooledConnection; + } + + @SuppressWarnings("resource") // Caller closes + private PooledConnectionImpl getPooledConnectionImpl(final String pooledUserName, final String pooledUserPassword) throws SQLException { + PooledConnectionImpl pooledConnection; + if (connectionProperties != null) { + pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), connectionProperties)); + } else { + pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword)); + } + pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); + return pooledConnection; + } + + /** + * Implements {@link Referenceable}. + */ + @Override + public Reference getReference() throws NamingException { + // this class implements its own factory + final String factory = getClass().getName(); + + final Reference ref = new Reference(getClass().getName(), factory, null); + + ref.add(new StringRefAddr(KEY_DESCRIPTION, getDescription())); + ref.add(new StringRefAddr(KEY_DRIVER, getDriver())); + ref.add(new StringRefAddr(KEY_LOGIN_TIMEOUT, String.valueOf(getLoginTimeout()))); + ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword())); + ref.add(new StringRefAddr(Constants.KEY_USER, getUser())); + ref.add(new StringRefAddr(KEY_URL, getUrl())); + + ref.add(new StringRefAddr(KEY_POOL_PREPARED_STATEMENTS, String.valueOf(isPoolPreparedStatements()))); + ref.add(new StringRefAddr(KEY_MAX_IDLE, String.valueOf(getMaxIdle()))); + ref.add(new StringRefAddr(KEY_NUM_TESTS_PER_EVICTION_RUN, String.valueOf(getNumTestsPerEvictionRun()))); + ref.add(new StringRefAddr(KEY_MAX_PREPARED_STATEMENTS, String.valueOf(getMaxPreparedStatements()))); + // + // Pair of current and deprecated. + ref.add(new StringRefAddr(KEY_DURATION_BETWEEN_EVICTION_RUNS, String.valueOf(getDurationBetweenEvictionRuns()))); + ref.add(new StringRefAddr(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS, String.valueOf(getTimeBetweenEvictionRunsMillis()))); + // + // Pair of current and deprecated. + ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_DURATION, String.valueOf(getMinEvictableIdleDuration()))); + ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(getMinEvictableIdleTimeMillis()))); + + return ref; + } + + private String getStringContent(final RefAddr ra) { + return ra.getContent().toString(); + } + + /** + * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no + * idle object evictor thread will be run. + * + * @return the value of the evictor thread timer + * @see #setDurationBetweenEvictionRuns(Duration) + * @deprecated Use {@link #getDurationBetweenEvictionRuns()}. + */ + @Deprecated + public long getTimeBetweenEvictionRunsMillis() { + return durationBetweenEvictionRuns.toMillis(); + } + + /** + * Gets the value of connection string used to locate the database for this data source. + * + * @return value of connection string. + */ + public String getUrl() { + return connectionString; + } + + /** + * Gets the value of default user (login or user name). + * + * @return value of user. + */ + public String getUser() { + return userName; + } + + /** + * Returns the value of the accessToUnderlyingConnectionAllowed property. + * + * @return true if access to the underlying is allowed, false otherwise. + */ + public synchronized boolean isAccessToUnderlyingConnectionAllowed() { + return this.accessToUnderlyingConnectionAllowed; + } + + private boolean isNotEmpty(final RefAddr ra) { + return ra != null && ra.getContent() != null; + } + + /** + * Whether to toggle the pooling of {@code PreparedStatement}s + * + * @return value of poolPreparedStatements. + */ + public boolean isPoolPreparedStatements() { + return poolPreparedStatements; + } + + /** + * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to + * the underlying connection. (Default: false) + * + * @param allow Access to the underlying connection is granted when true. + */ + public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { + this.accessToUnderlyingConnectionAllowed = allow; + } + + /** + * Sets the connection properties passed to the JDBC driver. + *

+ * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are + * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()} + * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when + * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or + * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not + * null. + *

+ * + * @param props Connection properties to use when creating new connections. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setConnectionProperties(final Properties props) { + assertInitializationAllowed(); + connectionProperties = props; + if (connectionProperties != null) { + final String user = connectionProperties.getProperty(Constants.KEY_USER); + if (user != null) { + setUser(user); + } + final String password = connectionProperties.getProperty(Constants.KEY_PASSWORD); + if (password != null) { + setPassword(password); + } + } + } + + /** + * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is + * not used internally. + * + * @param description Value to assign to description. + */ + public void setDescription(final String description) { + this.description = description; + } + + /** + * Sets the driver class name. Setting the driver class name cause the driver to be registered with the + * DriverManager. + * + * @param driver Value to assign to driver. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + * @throws ClassNotFoundException if the class cannot be located + */ + public void setDriver(final String driver) throws ClassNotFoundException { + assertInitializationAllowed(); + this.driver = driver; + // make sure driver is registered + Class.forName(driver); + } + + /** + * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no + * idle object evictor thread will be run. + * + * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor + * thread. When non-positive, no idle object evictor thread will be run. + * @see #getDurationBetweenEvictionRuns() + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + * @since 2.9.0 + */ + public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) { + assertInitializationAllowed(); + this.durationBetweenEvictionRuns = durationBetweenEvictionRuns; + } + + /** + * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT + * USED. + */ + @Override + public void setLoginTimeout(final int seconds) { + this.loginTimeout = seconds; + } + + /** + * Sets the log writer for this data source. NOT USED. + */ + @Override + public void setLogWriter(final PrintWriter logWriter) { + this.logWriter = logWriter; + } + + /** + * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or + * negative for no limit. + * + * @param maxIdle The maximum number of statements that can remain idle + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setMaxIdle(final int maxIdle) { + assertInitializationAllowed(); + this.maxIdle = maxIdle; + } + + /** + * Sets the maximum number of prepared statements. + * + * @param maxPreparedStatements the new maximum number of prepared statements + */ + public void setMaxPreparedStatements(final int maxPreparedStatements) { + this.maxPreparedStatements = maxPreparedStatements; + } + + /** + * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the + * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone. + * + * @param minEvictableIdleDuration minimum time to set in milliseconds. + * @see #getMinEvictableIdleDuration() + * @see #setDurationBetweenEvictionRuns(Duration) + * @throws IllegalStateException if {@link #getPooledConnection()} has been called. + * @since 2.9.0 + */ + public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) { + assertInitializationAllowed(); + this.minEvictableIdleDuration = minEvictableIdleDuration; + } + + /** + * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the + * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone. + * + * @param minEvictableIdleTimeMillis minimum time to set in milliseconds. + * @see #getMinEvictableIdleDuration() + * @see #setDurationBetweenEvictionRuns(Duration) + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}. + */ + @Deprecated + public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) { + assertInitializationAllowed(); + this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis); + } + + /** + * Sets the number of statements to examine during each run of the idle object evictor thread (if any). + *

+ * When a negative value is supplied, + * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run. + * I.e., when the value is -n, roughly one nth of the idle objects will be tested per run. + *

+ * + * @param numTestsPerEvictionRun number of statements to examine per run + * @see #getNumTestsPerEvictionRun() + * @see #setDurationBetweenEvictionRuns(Duration) + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { + assertInitializationAllowed(); + this.numTestsPerEvictionRun = numTestsPerEvictionRun; + } + + /** + * Sets the value of password for the default user. + * + * @param userPassword Value to assign to password. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setPassword(final char[] userPassword) { + assertInitializationAllowed(); + this.userPassword = Utils.clone(userPassword); + update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword)); + } + + /** + * Sets the value of password for the default user. + * + * @param userPassword Value to assign to password. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setPassword(final String userPassword) { + assertInitializationAllowed(); + this.userPassword = Utils.toCharArray(userPassword); + update(connectionProperties, Constants.KEY_PASSWORD, userPassword); + } + + /** + * Whether to toggle the pooling of {@code PreparedStatement}s + * + * @param poolPreparedStatements true to pool statements. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setPoolPreparedStatements(final boolean poolPreparedStatements) { + assertInitializationAllowed(); + this.poolPreparedStatements = poolPreparedStatements; + } + + /** + * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no + * idle object evictor thread will be run. + * + * @param timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle object evictor + * thread. When non-positive, no idle object evictor thread will be run. + * @see #getDurationBetweenEvictionRuns() + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}. + */ + @Deprecated + public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { + assertInitializationAllowed(); + this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis); + } + + /** + * Sets the value of URL string used to locate the database for this data source. + * + * @param connectionString Value to assign to connection string. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setUrl(final String connectionString) { + assertInitializationAllowed(); + this.connectionString = connectionString; + } + + /** + * Sets the value of default user (login or user name). + * + * @param userName Value to assign to user. + * @throws IllegalStateException if {@link #getPooledConnection()} has been called + */ + public void setUser(final String userName) { + assertInitializationAllowed(); + this.userName = userName; + update(connectionProperties, Constants.KEY_USER, userName); + } + + /** + * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties. + * + * @since 2.6.0 + */ + @Override + public synchronized String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + builder.append("[description="); + builder.append(description); + builder.append(", connectionString="); + // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string + // is not in a legal URL format? + builder.append(connectionString); + builder.append(", driver="); + builder.append(driver); + builder.append(", loginTimeout="); + builder.append(loginTimeout); + builder.append(", poolPreparedStatements="); + builder.append(poolPreparedStatements); + builder.append(", maxIdle="); + builder.append(maxIdle); + builder.append(", timeBetweenEvictionRunsMillis="); + builder.append(durationBetweenEvictionRuns); + builder.append(", numTestsPerEvictionRun="); + builder.append(numTestsPerEvictionRun); + builder.append(", minEvictableIdleTimeMillis="); + builder.append(minEvictableIdleDuration); + builder.append(", maxPreparedStatements="); + builder.append(maxPreparedStatements); + builder.append(", getConnectionCalled="); + builder.append(getConnectionCalled); + builder.append(", connectionProperties="); + builder.append(Utils.cloneWithoutCredentials(connectionProperties)); + builder.append(", accessToUnderlyingConnectionAllowed="); + builder.append(accessToUnderlyingConnectionAllowed); + builder.append("]"); + return builder.toString(); + } + + private void update(final Properties properties, final String key, final String value) { + if (properties != null && key != null) { + if (value == null) { + properties.remove(key); + } else { + properties.setProperty(key, value); + } + } + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java index a97fda5468..745be7f4ae 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java @@ -1,757 +1,757 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.cpdsadapter; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.sql.ConnectionEvent; -import javax.sql.ConnectionEventListener; -import javax.sql.PooledConnection; -import javax.sql.StatementEventListener; - -import org.apache.commons.dbcp2.DelegatingConnection; -import org.apache.commons.dbcp2.DelegatingPreparedStatement; -import org.apache.commons.dbcp2.Jdbc41Bridge; -import org.apache.commons.dbcp2.PStmtKey; -import org.apache.commons.dbcp2.PoolableCallableStatement; -import org.apache.commons.dbcp2.PoolablePreparedStatement; -import org.apache.commons.dbcp2.PoolingConnection.StatementType; -import org.apache.commons.dbcp2.Utils; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.KeyedPooledObjectFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultPooledObject; - -/** - * Implements {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}. - * - * @since 2.0 - */ -final class PooledConnectionImpl - implements PooledConnection, KeyedPooledObjectFactory { - - private static final String CLOSED = "Attempted to use PooledConnection after closed() was called."; - - /** - * The JDBC database connection that represents the physical db connection. - */ - private Connection connection; - - /** - * A DelegatingConnection used to create a PoolablePreparedStatementStub. - */ - private final DelegatingConnection delegatingConnection; - - /** - * The JDBC database logical connection. - */ - private Connection logicalConnection; - - /** - * ConnectionEventListeners. - */ - private final List eventListeners; - - /** - * StatementEventListeners. - */ - private final List statementEventListeners = Collections.synchronizedList(new ArrayList<>()); - - /** - * Flag set to true, once {@link #close()} is called. - */ - private boolean closed; - - /** My pool of {@link PreparedStatement}s. */ - private KeyedObjectPool pStmtPool; - - /** - * Controls access to the underlying connection. - */ - private boolean accessToUnderlyingConnectionAllowed; - - /** - * Wraps a real connection. - * - * @param connection - * the connection to be wrapped. - */ - PooledConnectionImpl(final Connection connection) { - this.connection = connection; - if (connection instanceof DelegatingConnection) { - this.delegatingConnection = (DelegatingConnection) connection; - } else { - this.delegatingConnection = new DelegatingConnection<>(connection); - } - eventListeners = Collections.synchronizedList(new ArrayList<>()); - closed = false; - } - - /** - * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s. - * - * @param pooledObject Activates the underlying object. - */ - @Override - public void activateObject(final PStmtKey key, final PooledObject pooledObject) - throws SQLException { - pooledObject.getObject().activate(); - } - - /** - * {@inheritDoc} - */ - @Override - public void addConnectionEventListener(final ConnectionEventListener listener) { - if (!eventListeners.contains(listener)) { - eventListeners.add(listener); - } - } - - @Override - public void addStatementEventListener(final StatementEventListener listener) { - if (!statementEventListeners.contains(listener)) { - statementEventListeners.add(listener); - } - } - - /** - * Throws an SQLException, if isClosed is true - */ - private void assertOpen() throws SQLException { - if (closed || connection == null) { - throw new SQLException(CLOSED); - } - } - - /** - * Closes the physical connection and marks this {@code PooledConnection} so that it may not be used to - * generate any more logical {@code Connection}s. - * - * @throws SQLException - * Thrown when an error occurs or the connection is already closed. - */ - @Override - public void close() throws SQLException { - assertOpen(); - closed = true; - try { - if (pStmtPool != null) { - try { - pStmtPool.close(); - } finally { - pStmtPool = null; - } - } - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Cannot close connection (return to pool failed)", e); - } finally { - try { - connection.close(); - } finally { - connection = null; - } - } - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @return a {@link PStmtKey} for the given arguments. - */ - protected PStmtKey createKey(final String sql) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull()); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param autoGeneratedKeys - * A flag indicating whether auto-generated keys should be returned; one of - * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param resultSetType - * A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param resultSetType - * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE} - * @param resultSetHoldability - * One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} - * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param resultSetType - * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE} - * @param resultSetConcurrency - * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @param resultSetHoldability - * One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} - * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. - * @param statementType - * The SQL statement type, prepared or callable. - * @return a key to uniquely identify a prepared statement. - * @since 2.4.0 - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability, - final StatementType statementType) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, statementType); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param resultSetType - * A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @param statementType - * The SQL statement type, prepared or callable. - * @return a key to uniquely identify a prepared statement. - * @since 2.4.0 - */ - protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param columnIndexes - * An array of column indexes indicating the columns that should be returned from the inserted row or - * rows. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final int[] columnIndexes) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnIndexes); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param statementType - * The SQL statement type, prepared or callable. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final StatementType statementType) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), statementType); - } - - /** - * Creates a {@link PStmtKey} for the given arguments. - * - * @param sql - * The SQL statement. - * @param columnNames - * An array of column names indicating the columns that should be returned from the inserted row or rows. - * @return a key to uniquely identify a prepared statement. - */ - protected PStmtKey createKey(final String sql, final String[] columnNames) { - return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnNames); - } - - /** - * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s. - * - * @param key - * ignored - * @param pooledObject - * the wrapped {@link PreparedStatement} to be destroyed. - */ - @Override - public void destroyObject(final PStmtKey key, final PooledObject pooledObject) throws SQLException { - if (pooledObject != null) { - @SuppressWarnings("resource") - final DelegatingPreparedStatement object = pooledObject.getObject(); - if (object != null) { - @SuppressWarnings("resource") - final Statement innermostDelegate = object.getInnermostDelegate(); - if (innermostDelegate != null) { - innermostDelegate.close(); - } - } - } - } - - /** - * Closes the physical connection and checks that the logical connection was closed as well. - */ - @Override - protected void finalize() throws Throwable { - // Closing the Connection ensures that if anyone tries to use it, - // an error will occur. - Utils.close(connection, null); - // make sure the last connection is marked as closed - if (logicalConnection != null && !logicalConnection.isClosed()) { - throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed."); - } - } - - private String getCatalogOrNull() { - try { - return connection == null ? null : connection.getCatalog(); - } catch (final SQLException e) { - return null; - } - } - - /** - * Returns a JDBC connection. - * - * @return The database connection. - * @throws SQLException - * if the connection is not open or the previous logical connection is still open - */ - @Override - public Connection getConnection() throws SQLException { - assertOpen(); - // make sure the last connection is marked as closed - if (logicalConnection != null && !logicalConnection.isClosed()) { - // should notify pool of error so the pooled connection can - // be removed !FIXME! - throw new SQLException("PooledConnection was reused, without its previous Connection being closed."); - } - - // the spec requires that this return a new Connection instance. - logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed()); - return logicalConnection; - } - - private Connection getRawConnection() throws SQLException { - assertOpen(); - return connection; - } - - private String getSchemaOrNull() { - try { - return connection == null ? null : Jdbc41Bridge.getSchema(connection); - } catch (final SQLException e) { - return null; - } - } - - /** - * Returns the value of the accessToUnderlyingConnectionAllowed property. - * - * @return true if access to the underlying is allowed, false otherwise. - */ - public synchronized boolean isAccessToUnderlyingConnectionAllowed() { - return this.accessToUnderlyingConnectionAllowed; - } - - /** - * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s. - * - * @param key - * The key for the {@link PreparedStatement} to be created. - */ - @SuppressWarnings("resource") - @Override - public PooledObject makeObject(final PStmtKey key) throws SQLException { - if (null == key) { - throw new IllegalArgumentException("Prepared statement key is null or invalid."); - } - if (key.getStmtType() == StatementType.PREPARED_STATEMENT) { - final PreparedStatement statement = (PreparedStatement) key.createStatement(connection); - @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this - final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, - delegatingConnection); - return new DefaultPooledObject<>(pps); - } - final CallableStatement statement = (CallableStatement) key.createStatement(connection); - @SuppressWarnings("unchecked") - final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, - (DelegatingConnection) delegatingConnection); - return new DefaultPooledObject<>(pcs); - } - - /** - * Sends a connectionClosed event. - */ - void notifyListeners() { - final ConnectionEvent event = new ConnectionEvent(this); - new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event)); - } - - /** - * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently, invokes - * {@link PreparedStatement#clearParameters}. - * - * @param key - * ignored - * @param pooledObject - * a wrapped {@link PreparedStatement} - */ - @Override - public void passivateObject(final PStmtKey key, final PooledObject pooledObject) - throws SQLException { - @SuppressWarnings("resource") - final DelegatingPreparedStatement dps = pooledObject.getObject(); - dps.clearParameters(); - dps.passivate(); - } - - /** - * Creates or obtains a {@link CallableStatement} from my pool. - * - * @param sql - * an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is - * specified using JDBC call escape syntax. - * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement. - * @throws SQLException - * Thrown if a database access error occurs or this method is called on a closed connection. - * @since 2.4.0 - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - CallableStatement prepareCall(final String sql) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareCall(sql); - } - try { - return (CallableStatement) pStmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareCall from pool failed", e); - } - } - - /** - * Creates or obtains a {@link CallableStatement} from my pool. - * - * @param sql - * a {@code String} object that is the SQL statement to be sent to the database; may contain on or - * more '?' parameters. - * @param resultSetType - * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce - * {@code ResultSet} objects with the given type and concurrency. - * @throws SQLException - * Thrown if a database access error occurs, this method is called on a closed connection or the given - * parameters are not {@code ResultSet} constants indicating type and concurrency. - * @since 2.4.0 - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency); - } - try { - return (CallableStatement) pStmtPool.borrowObject( - createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareCall from pool failed", e); - } - } - - /** - * Creates or obtains a {@link CallableStatement} from my pool. - * - * @param sql - * a {@code String} object that is the SQL statement to be sent to the database; may contain on or - * more '?' parameters. - * @param resultSetType - * one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * @param resultSetHoldability - * one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} - * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. - * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will - * generate {@code ResultSet} objects with the given type, concurrency, and holdability. - * @throws SQLException - * Thrown if a database access error occurs, this method is called on a closed connection or the given - * parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability. - * @since 2.4.0 - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); - } - try { - return (CallableStatement) pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, - resultSetHoldability, StatementType.CALLABLE_STATEMENT)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareCall from pool failed", e); - } - } - - /** - * Creates or obtains a {@link PreparedStatement} from my pool. - * - * @param sql the SQL statement. - * @return a {@link PoolablePreparedStatement} - * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or - * the borrow failed. - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql); - } - try { - return pStmtPool.borrowObject(createKey(sql)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - /** - * Creates or obtains a {@link PreparedStatement} from my pool. - * - * @param sql - * an SQL statement that may contain one or more '?' IN parameter placeholders. - * @param autoGeneratedKeys - * a flag indicating whether auto-generated keys should be returned; one of - * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. - * @return a {@link PoolablePreparedStatement} - * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or - * the borrow failed. - * @see Connection#prepareStatement(String, int) - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql, autoGeneratedKeys); - } - try { - return pStmtPool.borrowObject(createKey(sql, autoGeneratedKeys)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - /** - * Creates or obtains a {@link PreparedStatement} from my pool. - * - * @param sql - * a {@code String} object that is the SQL statement to be sent to the database; may contain one or - * more '?' IN parameters. - * @param resultSetType - * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, - * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. - * @param resultSetConcurrency - * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or - * {@code ResultSet.CONCUR_UPDATABLE}. - * - * @return a {@link PoolablePreparedStatement}. - * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or - * the borrow failed. - * @see Connection#prepareStatement(String, int, int) - */ - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) - throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency); - } - try { - return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, - final int resultSetHoldability) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); - } - try { - return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql, columnIndexes); - } - try { - return pStmtPool.borrowObject(createKey(sql, columnIndexes)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - @SuppressWarnings("resource") // getRawConnection() does not allocate - PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { - if (pStmtPool == null) { - return getRawConnection().prepareStatement(sql, columnNames); - } - try { - return pStmtPool.borrowObject(createKey(sql, columnNames)); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void removeConnectionEventListener(final ConnectionEventListener listener) { - eventListeners.remove(listener); - } - - @Override - public void removeStatementEventListener(final StatementEventListener listener) { - statementEventListeners.remove(listener); - } - - /** - * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to - * the underlying connection. (Default: false.) - * - * @param allow - * Access to the underlying connection is granted when true. - */ - public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { - this.accessToUnderlyingConnectionAllowed = allow; - } - - public void setStatementPool(final KeyedObjectPool statementPool) { - pStmtPool = statementPool; - } - - /** - * @since 2.6.0 - */ - @Override - public synchronized String toString() { - final StringBuilder builder = new StringBuilder(super.toString()); - builder.append("[connection="); - builder.append(connection); - builder.append(", delegatingConnection="); - builder.append(delegatingConnection); - builder.append(", logicalConnection="); - builder.append(logicalConnection); - builder.append(", eventListeners="); - builder.append(eventListeners); - builder.append(", statementEventListeners="); - builder.append(statementEventListeners); - builder.append(", closed="); - builder.append(closed); - builder.append(", pStmtPool="); - builder.append(pStmtPool); - builder.append(", accessToUnderlyingConnectionAllowed="); - builder.append(accessToUnderlyingConnectionAllowed); - builder.append("]"); - return builder.toString(); - } - - /** - * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s. - * - * @param key - * Ignored. - * @param pooledObject - * Ignored. - * @return {@code true} - */ - @Override - public boolean validateObject(final PStmtKey key, final PooledObject pooledObject) { - return true; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.cpdsadapter; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.PooledConnection; +import javax.sql.StatementEventListener; + +import org.apache.commons.dbcp2.DelegatingConnection; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.Jdbc41Bridge; +import org.apache.commons.dbcp2.PStmtKey; +import org.apache.commons.dbcp2.PoolableCallableStatement; +import org.apache.commons.dbcp2.PoolablePreparedStatement; +import org.apache.commons.dbcp2.PoolingConnection.StatementType; +import org.apache.commons.dbcp2.Utils; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +/** + * Implements {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}. + * + * @since 2.0 + */ +final class PooledConnectionImpl + implements PooledConnection, KeyedPooledObjectFactory { + + private static final String CLOSED = "Attempted to use PooledConnection after closed() was called."; + + /** + * The JDBC database connection that represents the physical db connection. + */ + private Connection connection; + + /** + * A DelegatingConnection used to create a PoolablePreparedStatementStub. + */ + private final DelegatingConnection delegatingConnection; + + /** + * The JDBC database logical connection. + */ + private Connection logicalConnection; + + /** + * ConnectionEventListeners. + */ + private final List eventListeners; + + /** + * StatementEventListeners. + */ + private final List statementEventListeners = Collections.synchronizedList(new ArrayList<>()); + + /** + * Flag set to true, once {@link #close()} is called. + */ + private boolean closed; + + /** My pool of {@link PreparedStatement}s. */ + private KeyedObjectPool pStmtPool; + + /** + * Controls access to the underlying connection. + */ + private boolean accessToUnderlyingConnectionAllowed; + + /** + * Wraps a real connection. + * + * @param connection + * the connection to be wrapped. + */ + PooledConnectionImpl(final Connection connection) { + this.connection = connection; + if (connection instanceof DelegatingConnection) { + this.delegatingConnection = (DelegatingConnection) connection; + } else { + this.delegatingConnection = new DelegatingConnection<>(connection); + } + eventListeners = Collections.synchronizedList(new ArrayList<>()); + closed = false; + } + + /** + * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s. + * + * @param pooledObject Activates the underlying object. + */ + @Override + public void activateObject(final PStmtKey key, final PooledObject pooledObject) + throws SQLException { + pooledObject.getObject().activate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addConnectionEventListener(final ConnectionEventListener listener) { + if (!eventListeners.contains(listener)) { + eventListeners.add(listener); + } + } + + @Override + public void addStatementEventListener(final StatementEventListener listener) { + if (!statementEventListeners.contains(listener)) { + statementEventListeners.add(listener); + } + } + + /** + * Throws an SQLException, if isClosed is true + */ + private void assertOpen() throws SQLException { + if (closed || connection == null) { + throw new SQLException(CLOSED); + } + } + + /** + * Closes the physical connection and marks this {@code PooledConnection} so that it may not be used to + * generate any more logical {@code Connection}s. + * + * @throws SQLException + * Thrown when an error occurs or the connection is already closed. + */ + @Override + public void close() throws SQLException { + assertOpen(); + closed = true; + try { + if (pStmtPool != null) { + try { + pStmtPool.close(); + } finally { + pStmtPool = null; + } + } + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Cannot close connection (return to pool failed)", e); + } finally { + try { + connection.close(); + } finally { + connection = null; + } + } + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @return a {@link PStmtKey} for the given arguments. + */ + protected PStmtKey createKey(final String sql) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull()); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param autoGeneratedKeys + * A flag indicating whether auto-generated keys should be returned; one of + * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param resultSetType + * A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param resultSetType + * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE} + * @param resultSetHoldability + * One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} + * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param resultSetType + * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE} + * @param resultSetConcurrency + * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @param resultSetHoldability + * One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} + * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. + * @param statementType + * The SQL statement type, prepared or callable. + * @return a key to uniquely identify a prepared statement. + * @since 2.4.0 + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability, + final StatementType statementType) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, statementType); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param resultSetType + * A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @param statementType + * The SQL statement type, prepared or callable. + * @return a key to uniquely identify a prepared statement. + * @since 2.4.0 + */ + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param columnIndexes + * An array of column indexes indicating the columns that should be returned from the inserted row or + * rows. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final int[] columnIndexes) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnIndexes); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param statementType + * The SQL statement type, prepared or callable. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final StatementType statementType) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), statementType); + } + + /** + * Creates a {@link PStmtKey} for the given arguments. + * + * @param sql + * The SQL statement. + * @param columnNames + * An array of column names indicating the columns that should be returned from the inserted row or rows. + * @return a key to uniquely identify a prepared statement. + */ + protected PStmtKey createKey(final String sql, final String[] columnNames) { + return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnNames); + } + + /** + * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s. + * + * @param key + * ignored + * @param pooledObject + * the wrapped {@link PreparedStatement} to be destroyed. + */ + @Override + public void destroyObject(final PStmtKey key, final PooledObject pooledObject) throws SQLException { + if (pooledObject != null) { + @SuppressWarnings("resource") + final DelegatingPreparedStatement object = pooledObject.getObject(); + if (object != null) { + @SuppressWarnings("resource") + final Statement innermostDelegate = object.getInnermostDelegate(); + if (innermostDelegate != null) { + innermostDelegate.close(); + } + } + } + } + + /** + * Closes the physical connection and checks that the logical connection was closed as well. + */ + @Override + protected void finalize() throws Throwable { + // Closing the Connection ensures that if anyone tries to use it, + // an error will occur. + Utils.close(connection, null); + // make sure the last connection is marked as closed + if (logicalConnection != null && !logicalConnection.isClosed()) { + throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed."); + } + } + + private String getCatalogOrNull() { + try { + return connection == null ? null : connection.getCatalog(); + } catch (final SQLException e) { + return null; + } + } + + /** + * Returns a JDBC connection. + * + * @return The database connection. + * @throws SQLException + * if the connection is not open or the previous logical connection is still open + */ + @Override + public Connection getConnection() throws SQLException { + assertOpen(); + // make sure the last connection is marked as closed + if (logicalConnection != null && !logicalConnection.isClosed()) { + // should notify pool of error so the pooled connection can + // be removed !FIXME! + throw new SQLException("PooledConnection was reused, without its previous Connection being closed."); + } + + // the spec requires that this return a new Connection instance. + logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed()); + return logicalConnection; + } + + private Connection getRawConnection() throws SQLException { + assertOpen(); + return connection; + } + + private String getSchemaOrNull() { + try { + return connection == null ? null : Jdbc41Bridge.getSchema(connection); + } catch (final SQLException e) { + return null; + } + } + + /** + * Returns the value of the accessToUnderlyingConnectionAllowed property. + * + * @return true if access to the underlying is allowed, false otherwise. + */ + public synchronized boolean isAccessToUnderlyingConnectionAllowed() { + return this.accessToUnderlyingConnectionAllowed; + } + + /** + * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s. + * + * @param key + * The key for the {@link PreparedStatement} to be created. + */ + @SuppressWarnings("resource") + @Override + public PooledObject makeObject(final PStmtKey key) throws SQLException { + if (null == key) { + throw new IllegalArgumentException("Prepared statement key is null or invalid."); + } + if (key.getStmtType() == StatementType.PREPARED_STATEMENT) { + final PreparedStatement statement = (PreparedStatement) key.createStatement(connection); + @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this + final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, + delegatingConnection); + return new DefaultPooledObject<>(pps); + } + final CallableStatement statement = (CallableStatement) key.createStatement(connection); + @SuppressWarnings("unchecked") + final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, + (DelegatingConnection) delegatingConnection); + return new DefaultPooledObject<>(pcs); + } + + /** + * Sends a connectionClosed event. + */ + void notifyListeners() { + final ConnectionEvent event = new ConnectionEvent(this); + new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event)); + } + + /** + * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently, invokes + * {@link PreparedStatement#clearParameters}. + * + * @param key + * ignored + * @param pooledObject + * a wrapped {@link PreparedStatement} + */ + @Override + public void passivateObject(final PStmtKey key, final PooledObject pooledObject) + throws SQLException { + @SuppressWarnings("resource") + final DelegatingPreparedStatement dps = pooledObject.getObject(); + dps.clearParameters(); + dps.passivate(); + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is + * specified using JDBC call escape syntax. + * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement. + * @throws SQLException + * Thrown if a database access error occurs or this method is called on a closed connection. + * @since 2.4.0 + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + CallableStatement prepareCall(final String sql) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareCall(sql); + } + try { + return (CallableStatement) pStmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * a {@code String} object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce + * {@code ResultSet} objects with the given type and concurrency. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not {@code ResultSet} constants indicating type and concurrency. + * @since 2.4.0 + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency); + } + try { + return (CallableStatement) pStmtPool.borrowObject( + createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * a {@code String} object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * @param resultSetHoldability + * one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} + * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}. + * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will + * generate {@code ResultSet} objects with the given type, concurrency, and holdability. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability. + * @since 2.4.0 + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + try { + return (CallableStatement) pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, + resultSetHoldability, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } + } + + /** + * Creates or obtains a {@link PreparedStatement} from my pool. + * + * @param sql the SQL statement. + * @return a {@link PoolablePreparedStatement} + * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or + * the borrow failed. + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql); + } + try { + return pStmtPool.borrowObject(createKey(sql)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + /** + * Creates or obtains a {@link PreparedStatement} from my pool. + * + * @param sql + * an SQL statement that may contain one or more '?' IN parameter placeholders. + * @param autoGeneratedKeys + * a flag indicating whether auto-generated keys should be returned; one of + * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. + * @return a {@link PoolablePreparedStatement} + * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or + * the borrow failed. + * @see Connection#prepareStatement(String, int) + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql, autoGeneratedKeys); + } + try { + return pStmtPool.borrowObject(createKey(sql, autoGeneratedKeys)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + /** + * Creates or obtains a {@link PreparedStatement} from my pool. + * + * @param sql + * a {@code String} object that is the SQL statement to be sent to the database; may contain one or + * more '?' IN parameters. + * @param resultSetType + * a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}. + * @param resultSetConcurrency + * a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE}. + * + * @return a {@link PoolablePreparedStatement}. + * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or + * the borrow failed. + * @see Connection#prepareStatement(String, int, int) + */ + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) + throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency); + } + try { + return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, + final int resultSetHoldability) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + try { + return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql, columnIndexes); + } + try { + return pStmtPool.borrowObject(createKey(sql, columnIndexes)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + @SuppressWarnings("resource") // getRawConnection() does not allocate + PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + if (pStmtPool == null) { + return getRawConnection().prepareStatement(sql, columnNames); + } + try { + return pStmtPool.borrowObject(createKey(sql, columnNames)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareStatement from pool failed", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeConnectionEventListener(final ConnectionEventListener listener) { + eventListeners.remove(listener); + } + + @Override + public void removeStatementEventListener(final StatementEventListener listener) { + statementEventListeners.remove(listener); + } + + /** + * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to + * the underlying connection. (Default: false.) + * + * @param allow + * Access to the underlying connection is granted when true. + */ + public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { + this.accessToUnderlyingConnectionAllowed = allow; + } + + public void setStatementPool(final KeyedObjectPool statementPool) { + pStmtPool = statementPool; + } + + /** + * @since 2.6.0 + */ + @Override + public synchronized String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + builder.append("[connection="); + builder.append(connection); + builder.append(", delegatingConnection="); + builder.append(delegatingConnection); + builder.append(", logicalConnection="); + builder.append(logicalConnection); + builder.append(", eventListeners="); + builder.append(eventListeners); + builder.append(", statementEventListeners="); + builder.append(statementEventListeners); + builder.append(", closed="); + builder.append(closed); + builder.append(", pStmtPool="); + builder.append(pStmtPool); + builder.append(", accessToUnderlyingConnectionAllowed="); + builder.append(accessToUnderlyingConnectionAllowed); + builder.append("]"); + return builder.toString(); + } + + /** + * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s. + * + * @param key + * Ignored. + * @param pooledObject + * Ignored. + * @return {@code true} + */ + @Override + public boolean validateObject(final PStmtKey key, final PooledObject pooledObject) { + return true; + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java index e1fb4d11e0..6942454a2e 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java @@ -1,280 +1,280 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Duration; - -import javax.sql.ConnectionEvent; -import javax.sql.ConnectionEventListener; -import javax.sql.ConnectionPoolDataSource; -import javax.sql.PooledConnection; - -import org.apache.commons.pool2.ObjectPool; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.PooledObjectFactory; -import org.apache.commons.pool2.impl.DefaultPooledObject; - -/** - * A {@link PooledObjectFactory} that creates {@link org.apache.commons.dbcp2.PoolableConnection PoolableConnection}s. - * - * @since 2.0 - */ -final class CPDSConnectionFactory extends AbstractConnectionFactory - implements PooledObjectFactory, ConnectionEventListener, PooledConnectionManager { - - private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection."; - - private ObjectPool pool; - private UserPassKey userPassKey; - - /** - * Creates a new {@code PoolableConnectionFactory}. - * - * @param cpds - * the ConnectionPoolDataSource from which to obtain PooledConnection's - * @param validationQuery - * a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one - * row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate - * connections. - * @param validationQueryTimeoutDuration - * Timeout Duration before validation fails - * @param rollbackAfterValidation - * whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s. - * @param userName - * The user name to use to create connections - * @param userPassword - * The password to use to create connections - * @since 2.10.0 - */ - public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, - final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName, - final char[] userPassword) { - super(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation); - this.userPassKey = new UserPassKey(userName, userPassword); - } - - @Override - public void activateObject(final PooledObject pooledObject) throws SQLException { - validateLifetime(pooledObject); - } - - /** - * Verifies that the user name matches the user whose connections are being managed by this factory and closes the - * pool if this is the case; otherwise does nothing. - */ - @Override - public void closePool(final String userName) throws SQLException { - synchronized (this) { - if (userName == null || !userName.equals(this.userPassKey.getUserName())) { - return; - } - } - try { - pool.close(); - } catch (final Exception ex) { - throw new SQLException("Error closing connection pool", ex); - } - } - - /** - * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the - * user calls the close() method of this connection object. What we need to do here is to release this - * PooledConnection from our pool... - */ - @Override - public void connectionClosed(final ConnectionEvent event) { - final PooledConnection pc = (PooledConnection) event.getSource(); - // if this event occurred because we were validating, ignore it - // otherwise return the connection to the pool. - if (!validatingSet.contains(pc)) { - final PooledConnectionAndInfo pci = pcMap.get(pc); - if (pci == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - - try { - pool.returnObject(pci); - } catch (final Exception e) { - System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); - pc.removeConnectionEventListener(this); - try { - doDestroyObject(pci); - } catch (final Exception e2) { - System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); - e2.printStackTrace(); - } - } - } - } - - /** - * If a fatal error occurs, close the underlying physical connection so as not to be returned in the future - */ - @Override - public void connectionErrorOccurred(final ConnectionEvent event) { - final PooledConnection pc = (PooledConnection) event.getSource(); - if (null != event.getSQLException()) { - System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); - } - pc.removeConnectionEventListener(this); - - final PooledConnectionAndInfo pci = pcMap.get(pc); - if (pci == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - try { - pool.invalidateObject(pci); - } catch (final Exception e) { - System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); - e.printStackTrace(); - } - } - - /** - * Closes the PooledConnection and stops listening for events from it. - */ - @Override - public void destroyObject(final PooledObject p) throws SQLException { - doDestroyObject(p.getObject()); - } - - private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException { - final PooledConnection pc = pci.getPooledConnection(); - pc.removeConnectionEventListener(this); - pcMap.remove(pc); - pc.close(); - } - - /** - * (Testing API) Gets the value of password for the default user. - * - * @return value of password. - */ - char[] getPasswordCharArray() { - return userPassKey.getPasswordCharArray(); - } - - /** - * Returns the object pool used to pool connections created by this factory. - * - * @return ObjectPool managing pooled connections - */ - public ObjectPool getPool() { - return pool; - } - - /** - * Invalidates the PooledConnection in the pool. The CPDSConnectionFactory closes the connection and pool counters - * are updated appropriately. Also closes the pool. This ensures that all idle connections are closed and - * connections that are checked out are closed on return. - */ - @Override - public void invalidate(final PooledConnection pc) throws SQLException { - final PooledConnectionAndInfo pci = pcMap.get(pc); - if (pci == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - try { - pool.invalidateObject(pci); // Destroy instance and update pool counters - pool.close(); // Clear any other instances in this pool and kill others as they come back - } catch (final Exception ex) { - throw new SQLException("Error invalidating connection", ex); - } - } - - @Override - public synchronized PooledObject makeObject() throws SQLException { - PooledConnection pc = null; - if (userPassKey.getUserName() == null) { - pc = cpds.getPooledConnection(); - } else { - pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword()); - } - if (pc == null) { - throw new IllegalStateException("Connection pool data source returned null from getPooledConnection"); - } - // should we add this object as a listener or the pool. - // consider the validateObject method in decision - pc.addConnectionEventListener(this); - final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey); - pcMap.put(pc, pci); - return new DefaultPooledObject<>(pci); - } - - @Override - public void passivateObject(final PooledObject p) throws SQLException { - validateLifetime(p); - } - - /** - * Sets the database password used when creating new connections. - * - * @param userPassword - * new password - */ - public synchronized void setPassword(final char[] userPassword) { - this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword); - } - - /** - * Sets the database password used when creating new connections. - * - * @param userPassword - * new password - */ - @Override - public synchronized void setPassword(final String userPassword) { - this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword); - } - - /** - * - * @param pool - * the {@link ObjectPool} in which to pool those {@link Connection}s - */ - public void setPool(final ObjectPool pool) { - this.pool = pool; - } - - /** - * @since 2.6.0 - */ - @Override - public synchronized String toString() { - final StringBuilder builder = new StringBuilder(super.toString()); - builder.append("[cpds="); - builder.append(cpds); - builder.append(", validationQuery="); - builder.append(validationQuery); - builder.append(", validationQueryTimeoutDuration="); - builder.append(validationQueryTimeoutDuration); - builder.append(", rollbackAfterValidation="); - builder.append(rollbackAfterValidation); - builder.append(", pool="); - builder.append(pool); - builder.append(", maxConnDuration="); - builder.append(maxConnDuration); - builder.append(", validatingSet="); - builder.append(validatingSet); - builder.append(", pcMap="); - builder.append(pcMap); - builder.append("]"); - return builder.toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.PooledConnection; + +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.PooledObjectFactory; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +/** + * A {@link PooledObjectFactory} that creates {@link org.apache.commons.dbcp2.PoolableConnection PoolableConnection}s. + * + * @since 2.0 + */ +final class CPDSConnectionFactory extends AbstractConnectionFactory + implements PooledObjectFactory, ConnectionEventListener, PooledConnectionManager { + + private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection."; + + private ObjectPool pool; + private UserPassKey userPassKey; + + /** + * Creates a new {@code PoolableConnectionFactory}. + * + * @param cpds + * the ConnectionPoolDataSource from which to obtain PooledConnection's + * @param validationQuery + * a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one + * row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate + * connections. + * @param validationQueryTimeoutDuration + * Timeout Duration before validation fails + * @param rollbackAfterValidation + * whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s. + * @param userName + * The user name to use to create connections + * @param userPassword + * The password to use to create connections + * @since 2.10.0 + */ + public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, + final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName, + final char[] userPassword) { + super(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation); + this.userPassKey = new UserPassKey(userName, userPassword); + } + + @Override + public void activateObject(final PooledObject pooledObject) throws SQLException { + validateLifetime(pooledObject); + } + + /** + * Verifies that the user name matches the user whose connections are being managed by this factory and closes the + * pool if this is the case; otherwise does nothing. + */ + @Override + public void closePool(final String userName) throws SQLException { + synchronized (this) { + if (userName == null || !userName.equals(this.userPassKey.getUserName())) { + return; + } + } + try { + pool.close(); + } catch (final Exception ex) { + throw new SQLException("Error closing connection pool", ex); + } + } + + /** + * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the + * user calls the close() method of this connection object. What we need to do here is to release this + * PooledConnection from our pool... + */ + @Override + public void connectionClosed(final ConnectionEvent event) { + final PooledConnection pc = (PooledConnection) event.getSource(); + // if this event occurred because we were validating, ignore it + // otherwise return the connection to the pool. + if (!validatingSet.contains(pc)) { + final PooledConnectionAndInfo pci = pcMap.get(pc); + if (pci == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + + try { + pool.returnObject(pci); + } catch (final Exception e) { + System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); + pc.removeConnectionEventListener(this); + try { + doDestroyObject(pci); + } catch (final Exception e2) { + System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); + e2.printStackTrace(); + } + } + } + } + + /** + * If a fatal error occurs, close the underlying physical connection so as not to be returned in the future + */ + @Override + public void connectionErrorOccurred(final ConnectionEvent event) { + final PooledConnection pc = (PooledConnection) event.getSource(); + if (null != event.getSQLException()) { + System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); + } + pc.removeConnectionEventListener(this); + + final PooledConnectionAndInfo pci = pcMap.get(pc); + if (pci == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + try { + pool.invalidateObject(pci); + } catch (final Exception e) { + System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); + e.printStackTrace(); + } + } + + /** + * Closes the PooledConnection and stops listening for events from it. + */ + @Override + public void destroyObject(final PooledObject p) throws SQLException { + doDestroyObject(p.getObject()); + } + + private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException { + final PooledConnection pc = pci.getPooledConnection(); + pc.removeConnectionEventListener(this); + pcMap.remove(pc); + pc.close(); + } + + /** + * (Testing API) Gets the value of password for the default user. + * + * @return value of password. + */ + char[] getPasswordCharArray() { + return userPassKey.getPasswordCharArray(); + } + + /** + * Returns the object pool used to pool connections created by this factory. + * + * @return ObjectPool managing pooled connections + */ + public ObjectPool getPool() { + return pool; + } + + /** + * Invalidates the PooledConnection in the pool. The CPDSConnectionFactory closes the connection and pool counters + * are updated appropriately. Also closes the pool. This ensures that all idle connections are closed and + * connections that are checked out are closed on return. + */ + @Override + public void invalidate(final PooledConnection pc) throws SQLException { + final PooledConnectionAndInfo pci = pcMap.get(pc); + if (pci == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + try { + pool.invalidateObject(pci); // Destroy instance and update pool counters + pool.close(); // Clear any other instances in this pool and kill others as they come back + } catch (final Exception ex) { + throw new SQLException("Error invalidating connection", ex); + } + } + + @Override + public synchronized PooledObject makeObject() throws SQLException { + PooledConnection pc = null; + if (userPassKey.getUserName() == null) { + pc = cpds.getPooledConnection(); + } else { + pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword()); + } + if (pc == null) { + throw new IllegalStateException("Connection pool data source returned null from getPooledConnection"); + } + // should we add this object as a listener or the pool. + // consider the validateObject method in decision + pc.addConnectionEventListener(this); + final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey); + pcMap.put(pc, pci); + return new DefaultPooledObject<>(pci); + } + + @Override + public void passivateObject(final PooledObject p) throws SQLException { + validateLifetime(p); + } + + /** + * Sets the database password used when creating new connections. + * + * @param userPassword + * new password + */ + public synchronized void setPassword(final char[] userPassword) { + this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword); + } + + /** + * Sets the database password used when creating new connections. + * + * @param userPassword + * new password + */ + @Override + public synchronized void setPassword(final String userPassword) { + this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword); + } + + /** + * + * @param pool + * the {@link ObjectPool} in which to pool those {@link Connection}s + */ + public void setPool(final ObjectPool pool) { + this.pool = pool; + } + + /** + * @since 2.6.0 + */ + @Override + public synchronized String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + builder.append("[cpds="); + builder.append(cpds); + builder.append(", validationQuery="); + builder.append(validationQuery); + builder.append(", validationQueryTimeoutDuration="); + builder.append(validationQueryTimeoutDuration); + builder.append(", rollbackAfterValidation="); + builder.append(rollbackAfterValidation); + builder.append(", pool="); + builder.append(pool); + builder.append(", maxConnDuration="); + builder.append(maxConnDuration); + builder.append(", validatingSet="); + builder.append(validatingSet); + builder.append(", pcMap="); + builder.append(pcMap); + builder.append("]"); + return builder.toString(); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java b/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java index 0d9b329218..c54d6f4b1f 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java @@ -1,84 +1,84 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2.datasources; - -import java.io.Serializable; -import java.util.Arrays; - -import org.apache.commons.dbcp2.Utils; - -/** - * A {@code char} array wrapper that does not reveal its contents inadvertently through toString(). In contrast to, for - * example, AtomicReference which toString()'s its contents. - * - * May contain null. - * - * @since 2.9.0 - */ -final class CharArray implements Serializable { - - private static final long serialVersionUID = 1L; - - static final CharArray NULL = new CharArray((char[]) null); - - private final char[] chars; - - CharArray(final char[] chars) { - this.chars = Utils.clone(chars); - } - - CharArray(final String string) { - this.chars = Utils.toCharArray(string); - } - - /** - * Converts the value of char array as a String. - * - * @return value as a string, may be null. - */ - String asString() { - return Utils.toString(chars); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof CharArray)) { - return false; - } - final CharArray other = (CharArray) obj; - return Arrays.equals(chars, other.chars); - } - - /** - * Gets the value of char array. - * - * @return value, may be null. - */ - char[] get() { - return Utils.clone(chars); - } - - @Override - public int hashCode() { - return Arrays.hashCode(chars); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2.datasources; + +import java.io.Serializable; +import java.util.Arrays; + +import org.apache.commons.dbcp2.Utils; + +/** + * A {@code char} array wrapper that does not reveal its contents inadvertently through toString(). In contrast to, for + * example, AtomicReference which toString()'s its contents. + * + * May contain null. + * + * @since 2.9.0 + */ +final class CharArray implements Serializable { + + private static final long serialVersionUID = 1L; + + static final CharArray NULL = new CharArray((char[]) null); + + private final char[] chars; + + CharArray(final char[] chars) { + this.chars = Utils.clone(chars); + } + + CharArray(final String string) { + this.chars = Utils.toCharArray(string); + } + + /** + * Converts the value of char array as a String. + * + * @return value as a string, may be null. + */ + String asString() { + return Utils.toString(chars); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CharArray)) { + return false; + } + final CharArray other = (CharArray) obj; + return Arrays.equals(chars, other.chars); + } + + /** + * Gets the value of char array. + * + * @return value, may be null. + */ + char[] get() { + return Utils.clone(chars); + } + + @Override + public int hashCode() { + return Arrays.hashCode(chars); + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java index a14a3386cc..85d99e64a9 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java @@ -1,1321 +1,1321 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Serializable; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.time.Duration; -import java.util.Properties; -import java.util.logging.Logger; - -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.Referenceable; -import javax.sql.ConnectionPoolDataSource; -import javax.sql.DataSource; -import javax.sql.PooledConnection; - -import org.apache.commons.dbcp2.Utils; -import org.apache.commons.pool2.impl.BaseObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; -import org.apache.commons.pool2.impl.GenericObjectPool; - -/** - *

- * The base class for {@code SharedPoolDataSource} and {@code PerUserPoolDataSource}. Many of the - * configuration properties are shared and defined here. This class is declared public in order to allow particular - * usage with commons-beanutils; do not make direct use of it outside of commons-dbcp2. - *

- * - *

- * A J2EE container will normally provide some method of initializing the {@code DataSource} whose attributes are - * presented as bean getters/setters and then deploying it via JNDI. It is then available to an application as a source - * of pooled logical connections to the database. The pool needs a source of physical connections. This source is in the - * form of a {@code ConnectionPoolDataSource} that can be specified via the {@link #setDataSourceName(String)} used - * to lookup the source via JNDI. - *

- * - *

- * Although normally used within a JNDI environment, A DataSource can be instantiated and initialized as any bean. In - * this case the {@code ConnectionPoolDataSource} will likely be instantiated in a similar manner. This class - * allows the physical source of connections to be attached directly to this pool using the - * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method. - *

- * - *

- * The dbcp package contains an adapter, {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS}, that can be - * used to allow the use of {@code DataSource}'s based on this class with JDBC driver implementations that do not - * supply a {@code ConnectionPoolDataSource}, but still provide a {@link java.sql.Driver} implementation. - *

- * - *

- * The package documentation contains an example using Apache Tomcat and JNDI and it - * also contains a non-JNDI example. - *

- * - * @since 2.0 - */ -public abstract class InstanceKeyDataSource implements DataSource, Referenceable, Serializable, AutoCloseable { - - private static final long serialVersionUID = -6819270431752240878L; - - private static final String GET_CONNECTION_CALLED = "A Connection was already requested from this source, " - + "further initialization is not allowed."; - private static final String BAD_TRANSACTION_ISOLATION = "The requested TransactionIsolation level is invalid."; - - /** - * Internal constant to indicate the level is not set. - */ - protected static final int UNKNOWN_TRANSACTIONISOLATION = -1; - - /** Guards property setters - once true, setters throw IllegalStateException */ - private volatile boolean getConnectionCalled; - - /** Underlying source of PooledConnections */ - private ConnectionPoolDataSource dataSource; - - /** DataSource Name used to find the ConnectionPoolDataSource */ - private String dataSourceName; - - /** Description */ - private String description; - - /** Environment that may be used to set up a JNDI initial context. */ - private Properties jndiEnvironment; - - /** Login Timeout */ - private Duration loginTimeoutDuration = Duration.ZERO; - - /** Log stream */ - private PrintWriter logWriter; - - /** Instance key */ - private String instanceKey; - - // Pool properties - private boolean defaultBlockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED; - private String defaultEvictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME; - private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO; - private int defaultMaxIdle = GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY; - private int defaultMaxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; - private Duration defaultMaxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT; - private Duration defaultMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; - private int defaultMinIdle = GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY; - private int defaultNumTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; - private Duration defaultSoftMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION; - private boolean defaultTestOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE; - private boolean defaultTestOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW; - private boolean defaultTestOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN; - private boolean defaultTestWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE; - private Duration defaultDurationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; - - // Connection factory properties - private String validationQuery; - private Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); - private boolean rollbackAfterValidation; - private Duration maxConnDuration = Duration.ofMillis(-1); - - // Connection properties - private Boolean defaultAutoCommit; - private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION; - private Boolean defaultReadOnly; - - /** - * Default no-arg constructor for Serialization. - */ - public InstanceKeyDataSource() { - } - - /** - * Throws an IllegalStateException, if a PooledConnection has already been requested. - * - * @throws IllegalStateException Thrown if a PooledConnection has already been requested. - */ - protected void assertInitializationAllowed() throws IllegalStateException { - if (getConnectionCalled) { - throw new IllegalStateException(GET_CONNECTION_CALLED); - } - } - - /** - * Closes the connection pool being maintained by this datasource. - */ - @Override - public abstract void close() throws SQLException; - - private void closeDueToException(final PooledConnectionAndInfo info) { - if (info != null) { - try { - info.getPooledConnection().getConnection().close(); - } catch (final Exception e) { - // do not throw this exception because we are in the middle - // of handling another exception. But record it because - // it potentially leaks connections from the pool. - getLogWriter().println("[ERROR] Could not return connection to pool during exception handling. " + e.getMessage()); - } - } - } - - /** - * Attempts to establish a database connection. - */ - @Override - public Connection getConnection() throws SQLException { - return getConnection(null, null); - } - - /** - * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the - * provided user name and password. The password on the {@code PooledConnectionAndInfo} instance returned by - * {@code getPooledConnectionAndInfo} is compared to the {@code password} parameter. If the comparison - * fails, a database connection using the supplied user name and password is attempted. If the connection attempt - * fails, an SQLException is thrown, indicating that the given password did not match the password used to create - * the pooled connection. If the connection attempt succeeds, this means that the database password has been - * changed. In this case, the {@code PooledConnectionAndInfo} instance retrieved with the old password is - * destroyed and the {@code getPooledConnectionAndInfo} is repeatedly invoked until a - * {@code PooledConnectionAndInfo} instance with the new password is returned. - */ - @Override - public Connection getConnection(final String userName, final String userPassword) throws SQLException { - if (instanceKey == null) { - throw new SQLException("Must set the ConnectionPoolDataSource " - + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection."); - } - getConnectionCalled = true; - PooledConnectionAndInfo info = null; - try { - info = getPooledConnectionAndInfo(userName, userPassword); - } catch (final RuntimeException | SQLException e) { - closeDueToException(info); - throw e; - } catch (final Exception e) { - closeDueToException(info); - throw new SQLException("Cannot borrow connection from pool", e); - } - - // Password on PooledConnectionAndInfo does not match - if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) { - try { // See if password has changed by attempting connection - testCPDS(userName, userPassword); - } catch (final SQLException ex) { - // Password has not changed, so refuse client, but return connection to the pool - closeDueToException(info); - throw new SQLException( - "Given password did not match password used" + " to create the PooledConnection.", ex); - } catch (final javax.naming.NamingException ne) { - throw new SQLException("NamingException encountered connecting to database", ne); - } - /* - * Password must have changed -> destroy connection and keep retrying until we get a new, good one, - * destroying any idle connections with the old password as we pull them from the pool. - */ - final UserPassKey upkey = info.getUserPassKey(); - final PooledConnectionManager manager = getConnectionManager(upkey); - // Destroy and remove from pool - manager.invalidate(info.getPooledConnection()); - // Reset the password on the factory if using CPDSConnectionFactory - manager.setPassword(upkey.getPassword()); - info = null; - for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return - try { - info = getPooledConnectionAndInfo(userName, userPassword); - } catch (final RuntimeException | SQLException e) { - closeDueToException(info); - throw e; - } catch (final Exception e) { - closeDueToException(info); - throw new SQLException("Cannot borrow connection from pool", e); - } - if (info != null && userPassword != null && userPassword.equals(info.getPassword())) { - break; - } - if (info != null) { - manager.invalidate(info.getPooledConnection()); - } - info = null; - } - if (info == null) { - throw new SQLException("Cannot borrow connection from pool - password change failure."); - } - } - - final Connection connection = info.getPooledConnection().getConnection(); - try { - setupDefaults(connection, userName); - connection.clearWarnings(); - return connection; - } catch (final SQLException ex) { - Utils.close(connection, e -> getLogWriter().println("ignoring exception during close: " + e)); - throw ex; - } - } - - protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey); - - /** - * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being - * accessed via JNDI. - * - * @return value of connectionPoolDataSource. - */ - public ConnectionPoolDataSource getConnectionPoolDataSource() { - return dataSource; - } - - /** - * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source - * from a JNDI service provider. - * - * @return value of dataSourceName. - */ - public String getDataSourceName() { - return dataSourceName; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user - * pool. - */ - public boolean getDefaultBlockWhenExhausted() { - return this.defaultBlockWhenExhausted; - } - - /** - * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. - * - * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. - * @since 2.10.0 - */ - public Duration getDefaultDurationBetweenEvictionRuns() { - return this.defaultDurationBetweenEvictionRuns; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user - * pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user - * pool. - */ - public String getDefaultEvictionPolicyClassName() { - return this.defaultEvictionPolicyClassName; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. - */ - public boolean getDefaultLifo() { - return this.defaultLifo; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. - */ - public int getDefaultMaxIdle() { - return this.defaultMaxIdle; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. - */ - public int getDefaultMaxTotal() { - return this.defaultMaxTotal; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * @since 2.9.0 - */ - public Duration getDefaultMaxWait() { - return this.defaultMaxWaitDuration; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * @deprecated Use {@link #getDefaultMaxWait()}. - */ - @Deprecated - public long getDefaultMaxWaitMillis() { - return getDefaultMaxWait().toMillis(); - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user - * pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per - * user pool. - * @since 2.10.0 - */ - public Duration getDefaultMinEvictableIdleDuration() { - return this.defaultMinEvictableIdleDuration; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user - * pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per - * user pool. - * @deprecated Use {@link #getDefaultMinEvictableIdleDuration()}. - */ - @Deprecated - public long getDefaultMinEvictableIdleTimeMillis() { - return this.defaultMinEvictableIdleDuration.toMillis(); - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. - */ - public int getDefaultMinIdle() { - return this.defaultMinIdle; - } - - /** - * Gets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user - * pool. - * - * @return The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user - * pool. - */ - public int getDefaultNumTestsPerEvictionRun() { - return this.defaultNumTestsPerEvictionRun; - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * @since 2.10.0 - */ - public Duration getDefaultSoftMinEvictableIdleDuration() { - return this.defaultSoftMinEvictableIdleDuration; - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * @deprecated Use {@link #getDefaultSoftMinEvictableIdleDuration()}. - */ - @Deprecated - public long getDefaultSoftMinEvictableIdleTimeMillis() { - return this.defaultSoftMinEvictableIdleDuration.toMillis(); - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnBorrow()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnBorrow()} for each per user pool. - */ - public boolean getDefaultTestOnBorrow() { - return this.defaultTestOnBorrow; - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnCreate()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnCreate()} for each per user pool. - */ - public boolean getDefaultTestOnCreate() { - return this.defaultTestOnCreate; - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnReturn()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnReturn()} for each per user pool. - */ - public boolean getDefaultTestOnReturn() { - return this.defaultTestOnReturn; - } - - /** - * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestWhileIdle()} for each per user pool. - * - * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestWhileIdle()} for each per user pool. - */ - public boolean getDefaultTestWhileIdle() { - return this.defaultTestWhileIdle; - } - - /** - * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. - * - * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. - * @deprecated Use {@link #getDefaultDurationBetweenEvictionRuns()}. - */ - @Deprecated - public long getDefaultTimeBetweenEvictionRunsMillis() { - return this.defaultDurationBetweenEvictionRuns.toMillis(); - } - - /** - * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool. - * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns - * -1, the default is JDBC driver dependent. - * - * @return value of defaultTransactionIsolation. - */ - public int getDefaultTransactionIsolation() { - return defaultTransactionIsolation; - } - - /** - * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the - * datasource. It serves no internal purpose. - * - * @return value of description. - */ - public String getDescription() { - return description; - } - - /** - * Gets the instance key. - * - * @return the instance key. - */ - protected String getInstanceKey() { - return instanceKey; - } - - /** - * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is - * used to locate the back end ConnectionPoolDataSource. - * - * @param key - * JNDI environment key. - * @return value of jndiEnvironment. - */ - public String getJndiEnvironment(final String key) { - String value = null; - if (jndiEnvironment != null) { - value = jndiEnvironment.getProperty(key); - } - return value; - } - - /** - * Gets the value of loginTimeout. - * - * @return value of loginTimeout. - * @deprecated Use {@link #getLoginTimeoutDuration()}. - */ - @Deprecated - @Override - public int getLoginTimeout() { - return (int) loginTimeoutDuration.getSeconds(); - } - - /** - * Gets the value of loginTimeout. - * - * @return value of loginTimeout. - * @since 2.10.0 - */ - public Duration getLoginTimeoutDuration() { - return loginTimeoutDuration; - } - - /** - * Gets the value of logWriter. - * - * @return value of logWriter. - */ - @Override - public PrintWriter getLogWriter() { - if (logWriter == null) { - logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); - } - return logWriter; - } - - /** - * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an - * infinite lifetime. - * - * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an - * infinite lifetime. - * @since 2.10.0 - */ - public Duration getMaxConnDuration() { - return maxConnDuration; - } - - /** - * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an - * infinite lifetime. - * - * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an - * infinite lifetime. - * @deprecated Use {@link #getMaxConnDuration()}. - */ - @Deprecated - public Duration getMaxConnLifetime() { - return maxConnDuration; - } - - /** - * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an - * infinite lifetime. - * - * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an - * infinite lifetime. - * @deprecated Use {@link #getMaxConnLifetime()}. - */ - @Deprecated - public long getMaxConnLifetimeMillis() { - return maxConnDuration.toMillis(); - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(); - } - - /** - * This method is protected but can only be implemented in this package because PooledConnectionAndInfo is a package - * private type. - * - * @param userName The user name. - * @param userPassword The user password. - * @return Matching PooledConnectionAndInfo. - * @throws SQLException Connection or registration failure. - */ - protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword) - throws SQLException; - - /** - * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller. - * If specified, this query MUST be an SQL SELECT statement that returns at least one row. If not - * specified, {@link Connection#isValid(int)} will be used to validate connections. - * - * @return The SQL query that will be used to validate connections from this pool before returning them to the - * caller. - */ - public String getValidationQuery() { - return this.validationQuery; - } - - /** - * Returns the timeout in seconds before the validation query fails. - * - * @return The timeout in seconds before the validation query fails. - * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. - */ - @Deprecated - public int getValidationQueryTimeout() { - return (int) validationQueryTimeoutDuration.getSeconds(); - } - - /** - * Returns the timeout Duration before the validation query fails. - * - * @return The timeout Duration before the validation query fails. - */ - public Duration getValidationQueryTimeoutDuration() { - return validationQueryTimeoutDuration; - } - - /** - * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value - * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which - * will use the default value for the drive. - * - * @return value of defaultAutoCommit. - */ - public Boolean isDefaultAutoCommit() { - return defaultAutoCommit; - } - - /** - * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value - * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which - * will use the default value for the drive. - * - * @return value of defaultReadOnly. - */ - public Boolean isDefaultReadOnly() { - return defaultReadOnly; - } - - /** - * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from - * this pool before returning them to the caller. - * - * @return true if a rollback will be issued after executing the validation query - */ - public boolean isRollbackAfterValidation() { - return this.rollbackAfterValidation; - } - - @Override - public boolean isWrapperFor(final Class iface) throws SQLException { - return iface.isInstance(this); - } - - /** - * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the - * data source. - * - * @param dataSource - * Value to assign to connectionPoolDataSource. - */ - public void setConnectionPoolDataSource(final ConnectionPoolDataSource dataSource) { - assertInitializationAllowed(); - if (dataSourceName != null) { - throw new IllegalStateException("Cannot set the DataSource, if JNDI is used."); - } - if (this.dataSource != null) { - throw new IllegalStateException("The CPDS has already been set. It cannot be altered."); - } - this.dataSource = dataSource; - instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this); - } - - /** - * Sets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source - * from a JNDI service provider. - * - * @param dataSourceName - * Value to assign to dataSourceName. - */ - public void setDataSourceName(final String dataSourceName) { - assertInitializationAllowed(); - if (dataSource != null) { - throw new IllegalStateException("Cannot set the JNDI name for the DataSource, if already " - + "set using setConnectionPoolDataSource."); - } - if (this.dataSourceName != null) { - throw new IllegalStateException("The DataSourceName has already been set. " + "It cannot be altered."); - } - this.dataSourceName = dataSourceName; - instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this); - } - - /** - * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value - * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which - * will use the default value for the drive. - * - * @param defaultAutoCommit - * Value to assign to defaultAutoCommit. - */ - public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { - assertInitializationAllowed(); - this.defaultAutoCommit = defaultAutoCommit; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool. - * - * @param blockWhenExhausted - * The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user - * pool. - */ - public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) { - assertInitializationAllowed(); - this.defaultBlockWhenExhausted = blockWhenExhausted; - } - - /** - * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. - * - * @param defaultDurationBetweenEvictionRuns The default value for - * {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. - * @since 2.10.0 - */ - public void setDefaultDurationBetweenEvictionRuns(final Duration defaultDurationBetweenEvictionRuns) { - assertInitializationAllowed(); - this.defaultDurationBetweenEvictionRuns = defaultDurationBetweenEvictionRuns; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user - * pool. - * - * @param evictionPolicyClassName - * The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per - * user pool. - */ - public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) { - assertInitializationAllowed(); - this.defaultEvictionPolicyClassName = evictionPolicyClassName; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. - * - * @param lifo - * The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. - */ - public void setDefaultLifo(final boolean lifo) { - assertInitializationAllowed(); - this.defaultLifo = lifo; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. - * - * @param maxIdle - * The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. - */ - public void setDefaultMaxIdle(final int maxIdle) { - assertInitializationAllowed(); - this.defaultMaxIdle = maxIdle; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. - * - * @param maxTotal - * The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. - */ - public void setDefaultMaxTotal(final int maxTotal) { - assertInitializationAllowed(); - this.defaultMaxTotal = maxTotal; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * - * @param maxWaitMillis - * The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. - * @since 2.9.0 - */ - public void setDefaultMaxWait(final Duration maxWaitMillis) { - assertInitializationAllowed(); - this.defaultMaxWaitDuration = maxWaitMillis; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool. - * - * @param maxWaitMillis - * The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool. - * @deprecated Use {@link #setDefaultMaxWait(Duration)}. - */ - @Deprecated - public void setDefaultMaxWaitMillis(final long maxWaitMillis) { - setDefaultMaxWait(Duration.ofMillis(maxWaitMillis)); - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user - * pool. - * - * @param defaultMinEvictableIdleDuration - * The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each - * per user pool. - * @since 2.10.0 - */ - public void setDefaultMinEvictableIdle(final Duration defaultMinEvictableIdleDuration) { - assertInitializationAllowed(); - this.defaultMinEvictableIdleDuration = defaultMinEvictableIdleDuration; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user - * pool. - * - * @param minEvictableIdleTimeMillis - * The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each - * per user pool. - * @deprecated Use {@link #setDefaultMinEvictableIdle(Duration)}. - */ - @Deprecated - public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) { - assertInitializationAllowed(); - this.defaultMinEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis); - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. - * - * @param minIdle - * The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. - */ - public void setDefaultMinIdle(final int minIdle) { - assertInitializationAllowed(); - this.defaultMinIdle = minIdle; - } - - /** - * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user - * pool. - * - * @param numTestsPerEvictionRun - * The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per - * user pool. - */ - public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { - assertInitializationAllowed(); - this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun; - } - - /** - * Sets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value - * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which - * will use the default value for the drive. - * - * @param defaultReadOnly - * Value to assign to defaultReadOnly. - */ - public void setDefaultReadOnly(final Boolean defaultReadOnly) { - assertInitializationAllowed(); - this.defaultReadOnly = defaultReadOnly; - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * - * @param defaultSoftMinEvictableIdleDuration - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * @since 2.10.0 - */ - public void setDefaultSoftMinEvictableIdle(final Duration defaultSoftMinEvictableIdleDuration) { - assertInitializationAllowed(); - this.defaultSoftMinEvictableIdleDuration = defaultSoftMinEvictableIdleDuration; - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * - * @param softMinEvictableIdleTimeMillis - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. - * @deprecated Use {@link #setDefaultSoftMinEvictableIdle(Duration)}. - */ - @Deprecated - public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) { - assertInitializationAllowed(); - this.defaultSoftMinEvictableIdleDuration = Duration.ofMillis(softMinEvictableIdleTimeMillis); - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnBorrow()} for each per user pool. - * - * @param testOnBorrow - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnBorrow()} for each per user pool. - */ - public void setDefaultTestOnBorrow(final boolean testOnBorrow) { - assertInitializationAllowed(); - this.defaultTestOnBorrow = testOnBorrow; - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnCreate()} for each per user pool. - * - * @param testOnCreate - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnCreate()} for each per user pool. - */ - public void setDefaultTestOnCreate(final boolean testOnCreate) { - assertInitializationAllowed(); - this.defaultTestOnCreate = testOnCreate; - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnReturn()} for each per user pool. - * - * @param testOnReturn - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestOnReturn()} for each per user pool. - */ - public void setDefaultTestOnReturn(final boolean testOnReturn) { - assertInitializationAllowed(); - this.defaultTestOnReturn = testOnReturn; - } - - /** - * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestWhileIdle()} for each per user pool. - * - * @param testWhileIdle - * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool - * GenericObjectPool#getTestWhileIdle()} for each per user pool. - */ - public void setDefaultTestWhileIdle(final boolean testWhileIdle) { - assertInitializationAllowed(); - this.defaultTestWhileIdle = testWhileIdle; - } - - /** - * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. - * - * @param timeBetweenEvictionRunsMillis The default value for - * {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. - * @deprecated Use {@link #setDefaultDurationBetweenEvictionRuns(Duration)}. - */ - @Deprecated - public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { - assertInitializationAllowed(); - this.defaultDurationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis); - } - - /** - * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool. - * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC - * driver dependent. - * - * @param defaultTransactionIsolation - * Value to assign to defaultTransactionIsolation - */ - public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { - assertInitializationAllowed(); - switch (defaultTransactionIsolation) { - case Connection.TRANSACTION_NONE: - case Connection.TRANSACTION_READ_COMMITTED: - case Connection.TRANSACTION_READ_UNCOMMITTED: - case Connection.TRANSACTION_REPEATABLE_READ: - case Connection.TRANSACTION_SERIALIZABLE: - break; - default: - throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION); - } - this.defaultTransactionIsolation = defaultTransactionIsolation; - } - - /** - * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the - * datasource. It serves no internal purpose. - * - * @param description - * Value to assign to description. - */ - public void setDescription(final String description) { - this.description = description; - } - - /** - * Sets the JNDI environment to be used when instantiating a JNDI InitialContext. This InitialContext is used to - * locate the back end ConnectionPoolDataSource. - * - * @param properties - * the JNDI environment property to set which will overwrite any current settings - */ - void setJndiEnvironment(final Properties properties) { - if (jndiEnvironment == null) { - jndiEnvironment = new Properties(); - } else { - jndiEnvironment.clear(); - } - jndiEnvironment.putAll(properties); - } - - /** - * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This - * InitialContext is used to locate the back end ConnectionPoolDataSource. - * - * @param key - * the JNDI environment property to set. - * @param value - * the value assigned to specified JNDI environment property. - */ - public void setJndiEnvironment(final String key, final String value) { - if (jndiEnvironment == null) { - jndiEnvironment = new Properties(); - } - jndiEnvironment.setProperty(key, value); - } - - /** - * Sets the value of loginTimeout. - * - * @param loginTimeout - * Value to assign to loginTimeout. - * @since 2.10.0 - */ - public void setLoginTimeout(final Duration loginTimeout) { - this.loginTimeoutDuration = loginTimeout; - } - - /** - * Sets the value of loginTimeout. - * - * @param loginTimeout - * Value to assign to loginTimeout. - * @deprecated Use {@link #setLoginTimeout(Duration)}. - */ - @Deprecated - @Override - public void setLoginTimeout(final int loginTimeout) { - this.loginTimeoutDuration = Duration.ofSeconds(loginTimeout); - } - - /** - * Sets the value of logWriter. - * - * @param logWriter - * Value to assign to logWriter. - */ - @Override - public void setLogWriter(final PrintWriter logWriter) { - this.logWriter = logWriter; - } - - /** - *

- * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime. - *

- *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first time one of the following methods is - * invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()}, - * {@link #getLogWriter()}. - *

- * - * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime. - * @since 2.9.0 - */ - public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) { - this.maxConnDuration = maxConnLifetimeMillis; - } - - /** - *

- * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime. - *

- *

- * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first time one of the following methods is - * invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()}, - * {@link #getLogWriter()}. - *

- * - * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime. - * @deprecated Use {@link #setMaxConnLifetime(Duration)}. - */ - @Deprecated - public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { - setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis)); - } - - /** - * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from - * this pool before returning them to the caller. Default behavior is NOT to issue a rollback. The setting will only - * have an effect if a validation query is set - * - * @param rollbackAfterValidation - * new property value - */ - public void setRollbackAfterValidation(final boolean rollbackAfterValidation) { - assertInitializationAllowed(); - this.rollbackAfterValidation = rollbackAfterValidation; - } - - protected abstract void setupDefaults(Connection connection, String userName) throws SQLException; - - /** - * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller. - * If specified, this query MUST be an SQL SELECT statement that returns at least one row. If not - * specified, connections will be validated using {@link Connection#isValid(int)}. - * - * @param validationQuery - * The SQL query that will be used to validate connections from this pool before returning them to the - * caller. - */ - public void setValidationQuery(final String validationQuery) { - assertInitializationAllowed(); - this.validationQuery = validationQuery; - } - - /** - * Sets the timeout duration before the validation query fails. - * - * @param validationQueryTimeoutDuration - * The new timeout duration. - */ - public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { - this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; - } - - /** - * Sets the timeout in seconds before the validation query fails. - * - * @param validationQueryTimeoutSeconds - * The new timeout in seconds - * @deprecated Use {@link #setValidationQueryTimeout(Duration)}. - */ - @Deprecated - public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { - this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); - } - - protected ConnectionPoolDataSource testCPDS(final String userName, final String userPassword) - throws javax.naming.NamingException, SQLException { - // The source of physical database connections - ConnectionPoolDataSource cpds = this.dataSource; - if (cpds == null) { - Context ctx = null; - if (jndiEnvironment == null) { - ctx = new InitialContext(); - } else { - ctx = new InitialContext(jndiEnvironment); - } - final Object ds = ctx.lookup(dataSourceName); - if (!(ds instanceof ConnectionPoolDataSource)) { - throw new SQLException("Illegal configuration: " + "DataSource " + dataSourceName + " (" - + ds.getClass().getName() + ")" + " doesn't implement javax.sql.ConnectionPoolDataSource"); - } - cpds = (ConnectionPoolDataSource) ds; - } - - // try to get a connection with the supplied userName/password - PooledConnection conn = null; - try { - if (userName != null) { - conn = cpds.getPooledConnection(userName, userPassword); - } else { - conn = cpds.getPooledConnection(); - } - if (conn == null) { - throw new SQLException("Cannot connect using the supplied userName/password"); - } - } finally { - if (conn != null) { - try { - conn.close(); - } catch (final SQLException ignored) { - // at least we could connect - } - } - } - return cpds; - } - - /** - * @since 2.6.0 - */ - @Override - public synchronized String toString() { - final StringBuilder builder = new StringBuilder(super.toString()); - builder.append('['); - toStringFields(builder); - builder.append(']'); - return builder.toString(); - } - - protected void toStringFields(final StringBuilder builder) { - builder.append("getConnectionCalled="); - builder.append(getConnectionCalled); - builder.append(", dataSource="); - builder.append(dataSource); - builder.append(", dataSourceName="); - builder.append(dataSourceName); - builder.append(", description="); - builder.append(description); - builder.append(", jndiEnvironment="); - builder.append(jndiEnvironment); - builder.append(", loginTimeoutDuration="); - builder.append(loginTimeoutDuration); - builder.append(", logWriter="); - builder.append(logWriter); - builder.append(", instanceKey="); - builder.append(instanceKey); - builder.append(", defaultBlockWhenExhausted="); - builder.append(defaultBlockWhenExhausted); - builder.append(", defaultEvictionPolicyClassName="); - builder.append(defaultEvictionPolicyClassName); - builder.append(", defaultLifo="); - builder.append(defaultLifo); - builder.append(", defaultMaxIdle="); - builder.append(defaultMaxIdle); - builder.append(", defaultMaxTotal="); - builder.append(defaultMaxTotal); - builder.append(", defaultMaxWaitDuration="); - builder.append(defaultMaxWaitDuration); - builder.append(", defaultMinEvictableIdleDuration="); - builder.append(defaultMinEvictableIdleDuration); - builder.append(", defaultMinIdle="); - builder.append(defaultMinIdle); - builder.append(", defaultNumTestsPerEvictionRun="); - builder.append(defaultNumTestsPerEvictionRun); - builder.append(", defaultSoftMinEvictableIdleDuration="); - builder.append(defaultSoftMinEvictableIdleDuration); - builder.append(", defaultTestOnCreate="); - builder.append(defaultTestOnCreate); - builder.append(", defaultTestOnBorrow="); - builder.append(defaultTestOnBorrow); - builder.append(", defaultTestOnReturn="); - builder.append(defaultTestOnReturn); - builder.append(", defaultTestWhileIdle="); - builder.append(defaultTestWhileIdle); - builder.append(", defaultDurationBetweenEvictionRuns="); - builder.append(defaultDurationBetweenEvictionRuns); - builder.append(", validationQuery="); - builder.append(validationQuery); - builder.append(", validationQueryTimeoutDuration="); - builder.append(validationQueryTimeoutDuration); - builder.append(", rollbackAfterValidation="); - builder.append(rollbackAfterValidation); - builder.append(", maxConnDuration="); - builder.append(maxConnDuration); - builder.append(", defaultAutoCommit="); - builder.append(defaultAutoCommit); - builder.append(", defaultTransactionIsolation="); - builder.append(defaultTransactionIsolation); - builder.append(", defaultReadOnly="); - builder.append(defaultReadOnly); - } - - @Override - @SuppressWarnings("unchecked") - public T unwrap(final Class iface) throws SQLException { - if (isWrapperFor(iface)) { - return (T) this; - } - throw new SQLException(this + " is not a wrapper for " + iface); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.Properties; +import java.util.logging.Logger; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.Referenceable; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import javax.sql.PooledConnection; + +import org.apache.commons.dbcp2.Utils; +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericObjectPool; + +/** + *

+ * The base class for {@code SharedPoolDataSource} and {@code PerUserPoolDataSource}. Many of the + * configuration properties are shared and defined here. This class is declared public in order to allow particular + * usage with commons-beanutils; do not make direct use of it outside of commons-dbcp2. + *

+ * + *

+ * A J2EE container will normally provide some method of initializing the {@code DataSource} whose attributes are + * presented as bean getters/setters and then deploying it via JNDI. It is then available to an application as a source + * of pooled logical connections to the database. The pool needs a source of physical connections. This source is in the + * form of a {@code ConnectionPoolDataSource} that can be specified via the {@link #setDataSourceName(String)} used + * to lookup the source via JNDI. + *

+ * + *

+ * Although normally used within a JNDI environment, A DataSource can be instantiated and initialized as any bean. In + * this case the {@code ConnectionPoolDataSource} will likely be instantiated in a similar manner. This class + * allows the physical source of connections to be attached directly to this pool using the + * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method. + *

+ * + *

+ * The dbcp package contains an adapter, {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS}, that can be + * used to allow the use of {@code DataSource}'s based on this class with JDBC driver implementations that do not + * supply a {@code ConnectionPoolDataSource}, but still provide a {@link java.sql.Driver} implementation. + *

+ * + *

+ * The package documentation contains an example using Apache Tomcat and JNDI and it + * also contains a non-JNDI example. + *

+ * + * @since 2.0 + */ +public abstract class InstanceKeyDataSource implements DataSource, Referenceable, Serializable, AutoCloseable { + + private static final long serialVersionUID = -6819270431752240878L; + + private static final String GET_CONNECTION_CALLED = "A Connection was already requested from this source, " + + "further initialization is not allowed."; + private static final String BAD_TRANSACTION_ISOLATION = "The requested TransactionIsolation level is invalid."; + + /** + * Internal constant to indicate the level is not set. + */ + protected static final int UNKNOWN_TRANSACTIONISOLATION = -1; + + /** Guards property setters - once true, setters throw IllegalStateException */ + private volatile boolean getConnectionCalled; + + /** Underlying source of PooledConnections */ + private ConnectionPoolDataSource dataSource; + + /** DataSource Name used to find the ConnectionPoolDataSource */ + private String dataSourceName; + + /** Description */ + private String description; + + /** Environment that may be used to set up a JNDI initial context. */ + private Properties jndiEnvironment; + + /** Login Timeout */ + private Duration loginTimeoutDuration = Duration.ZERO; + + /** Log stream */ + private PrintWriter logWriter; + + /** Instance key */ + private String instanceKey; + + // Pool properties + private boolean defaultBlockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED; + private String defaultEvictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME; + private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO; + private int defaultMaxIdle = GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY; + private int defaultMaxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; + private Duration defaultMaxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT; + private Duration defaultMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION; + private int defaultMinIdle = GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY; + private int defaultNumTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; + private Duration defaultSoftMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION; + private boolean defaultTestOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE; + private boolean defaultTestOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW; + private boolean defaultTestOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN; + private boolean defaultTestWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE; + private Duration defaultDurationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS; + + // Connection factory properties + private String validationQuery; + private Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1); + private boolean rollbackAfterValidation; + private Duration maxConnDuration = Duration.ofMillis(-1); + + // Connection properties + private Boolean defaultAutoCommit; + private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION; + private Boolean defaultReadOnly; + + /** + * Default no-arg constructor for Serialization. + */ + public InstanceKeyDataSource() { + } + + /** + * Throws an IllegalStateException, if a PooledConnection has already been requested. + * + * @throws IllegalStateException Thrown if a PooledConnection has already been requested. + */ + protected void assertInitializationAllowed() throws IllegalStateException { + if (getConnectionCalled) { + throw new IllegalStateException(GET_CONNECTION_CALLED); + } + } + + /** + * Closes the connection pool being maintained by this datasource. + */ + @Override + public abstract void close() throws SQLException; + + private void closeDueToException(final PooledConnectionAndInfo info) { + if (info != null) { + try { + info.getPooledConnection().getConnection().close(); + } catch (final Exception e) { + // do not throw this exception because we are in the middle + // of handling another exception. But record it because + // it potentially leaks connections from the pool. + getLogWriter().println("[ERROR] Could not return connection to pool during exception handling. " + e.getMessage()); + } + } + } + + /** + * Attempts to establish a database connection. + */ + @Override + public Connection getConnection() throws SQLException { + return getConnection(null, null); + } + + /** + * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the + * provided user name and password. The password on the {@code PooledConnectionAndInfo} instance returned by + * {@code getPooledConnectionAndInfo} is compared to the {@code password} parameter. If the comparison + * fails, a database connection using the supplied user name and password is attempted. If the connection attempt + * fails, an SQLException is thrown, indicating that the given password did not match the password used to create + * the pooled connection. If the connection attempt succeeds, this means that the database password has been + * changed. In this case, the {@code PooledConnectionAndInfo} instance retrieved with the old password is + * destroyed and the {@code getPooledConnectionAndInfo} is repeatedly invoked until a + * {@code PooledConnectionAndInfo} instance with the new password is returned. + */ + @Override + public Connection getConnection(final String userName, final String userPassword) throws SQLException { + if (instanceKey == null) { + throw new SQLException("Must set the ConnectionPoolDataSource " + + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection."); + } + getConnectionCalled = true; + PooledConnectionAndInfo info = null; + try { + info = getPooledConnectionAndInfo(userName, userPassword); + } catch (final RuntimeException | SQLException e) { + closeDueToException(info); + throw e; + } catch (final Exception e) { + closeDueToException(info); + throw new SQLException("Cannot borrow connection from pool", e); + } + + // Password on PooledConnectionAndInfo does not match + if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) { + try { // See if password has changed by attempting connection + testCPDS(userName, userPassword); + } catch (final SQLException ex) { + // Password has not changed, so refuse client, but return connection to the pool + closeDueToException(info); + throw new SQLException( + "Given password did not match password used" + " to create the PooledConnection.", ex); + } catch (final javax.naming.NamingException ne) { + throw new SQLException("NamingException encountered connecting to database", ne); + } + /* + * Password must have changed -> destroy connection and keep retrying until we get a new, good one, + * destroying any idle connections with the old password as we pull them from the pool. + */ + final UserPassKey upkey = info.getUserPassKey(); + final PooledConnectionManager manager = getConnectionManager(upkey); + // Destroy and remove from pool + manager.invalidate(info.getPooledConnection()); + // Reset the password on the factory if using CPDSConnectionFactory + manager.setPassword(upkey.getPassword()); + info = null; + for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return + try { + info = getPooledConnectionAndInfo(userName, userPassword); + } catch (final RuntimeException | SQLException e) { + closeDueToException(info); + throw e; + } catch (final Exception e) { + closeDueToException(info); + throw new SQLException("Cannot borrow connection from pool", e); + } + if (info != null && userPassword != null && userPassword.equals(info.getPassword())) { + break; + } + if (info != null) { + manager.invalidate(info.getPooledConnection()); + } + info = null; + } + if (info == null) { + throw new SQLException("Cannot borrow connection from pool - password change failure."); + } + } + + final Connection connection = info.getPooledConnection().getConnection(); + try { + setupDefaults(connection, userName); + connection.clearWarnings(); + return connection; + } catch (final SQLException ex) { + Utils.close(connection, e -> getLogWriter().println("ignoring exception during close: " + e)); + throw ex; + } + } + + protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey); + + /** + * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being + * accessed via JNDI. + * + * @return value of connectionPoolDataSource. + */ + public ConnectionPoolDataSource getConnectionPoolDataSource() { + return dataSource; + } + + /** + * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source + * from a JNDI service provider. + * + * @return value of dataSourceName. + */ + public String getDataSourceName() { + return dataSourceName; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user + * pool. + */ + public boolean getDefaultBlockWhenExhausted() { + return this.defaultBlockWhenExhausted; + } + + /** + * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. + * + * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. + * @since 2.10.0 + */ + public Duration getDefaultDurationBetweenEvictionRuns() { + return this.defaultDurationBetweenEvictionRuns; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user + * pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user + * pool. + */ + public String getDefaultEvictionPolicyClassName() { + return this.defaultEvictionPolicyClassName; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. + */ + public boolean getDefaultLifo() { + return this.defaultLifo; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. + */ + public int getDefaultMaxIdle() { + return this.defaultMaxIdle; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. + */ + public int getDefaultMaxTotal() { + return this.defaultMaxTotal; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * @since 2.9.0 + */ + public Duration getDefaultMaxWait() { + return this.defaultMaxWaitDuration; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * @deprecated Use {@link #getDefaultMaxWait()}. + */ + @Deprecated + public long getDefaultMaxWaitMillis() { + return getDefaultMaxWait().toMillis(); + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user + * pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per + * user pool. + * @since 2.10.0 + */ + public Duration getDefaultMinEvictableIdleDuration() { + return this.defaultMinEvictableIdleDuration; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user + * pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per + * user pool. + * @deprecated Use {@link #getDefaultMinEvictableIdleDuration()}. + */ + @Deprecated + public long getDefaultMinEvictableIdleTimeMillis() { + return this.defaultMinEvictableIdleDuration.toMillis(); + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. + */ + public int getDefaultMinIdle() { + return this.defaultMinIdle; + } + + /** + * Gets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user + * pool. + * + * @return The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user + * pool. + */ + public int getDefaultNumTestsPerEvictionRun() { + return this.defaultNumTestsPerEvictionRun; + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * @since 2.10.0 + */ + public Duration getDefaultSoftMinEvictableIdleDuration() { + return this.defaultSoftMinEvictableIdleDuration; + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * @deprecated Use {@link #getDefaultSoftMinEvictableIdleDuration()}. + */ + @Deprecated + public long getDefaultSoftMinEvictableIdleTimeMillis() { + return this.defaultSoftMinEvictableIdleDuration.toMillis(); + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnBorrow()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnBorrow()} for each per user pool. + */ + public boolean getDefaultTestOnBorrow() { + return this.defaultTestOnBorrow; + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnCreate()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnCreate()} for each per user pool. + */ + public boolean getDefaultTestOnCreate() { + return this.defaultTestOnCreate; + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnReturn()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnReturn()} for each per user pool. + */ + public boolean getDefaultTestOnReturn() { + return this.defaultTestOnReturn; + } + + /** + * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestWhileIdle()} for each per user pool. + * + * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestWhileIdle()} for each per user pool. + */ + public boolean getDefaultTestWhileIdle() { + return this.defaultTestWhileIdle; + } + + /** + * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. + * + * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. + * @deprecated Use {@link #getDefaultDurationBetweenEvictionRuns()}. + */ + @Deprecated + public long getDefaultTimeBetweenEvictionRunsMillis() { + return this.defaultDurationBetweenEvictionRuns.toMillis(); + } + + /** + * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool. + * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns + * -1, the default is JDBC driver dependent. + * + * @return value of defaultTransactionIsolation. + */ + public int getDefaultTransactionIsolation() { + return defaultTransactionIsolation; + } + + /** + * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the + * datasource. It serves no internal purpose. + * + * @return value of description. + */ + public String getDescription() { + return description; + } + + /** + * Gets the instance key. + * + * @return the instance key. + */ + protected String getInstanceKey() { + return instanceKey; + } + + /** + * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is + * used to locate the back end ConnectionPoolDataSource. + * + * @param key + * JNDI environment key. + * @return value of jndiEnvironment. + */ + public String getJndiEnvironment(final String key) { + String value = null; + if (jndiEnvironment != null) { + value = jndiEnvironment.getProperty(key); + } + return value; + } + + /** + * Gets the value of loginTimeout. + * + * @return value of loginTimeout. + * @deprecated Use {@link #getLoginTimeoutDuration()}. + */ + @Deprecated + @Override + public int getLoginTimeout() { + return (int) loginTimeoutDuration.getSeconds(); + } + + /** + * Gets the value of loginTimeout. + * + * @return value of loginTimeout. + * @since 2.10.0 + */ + public Duration getLoginTimeoutDuration() { + return loginTimeoutDuration; + } + + /** + * Gets the value of logWriter. + * + * @return value of logWriter. + */ + @Override + public PrintWriter getLogWriter() { + if (logWriter == null) { + logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); + } + return logWriter; + } + + /** + * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an + * infinite lifetime. + * + * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an + * infinite lifetime. + * @since 2.10.0 + */ + public Duration getMaxConnDuration() { + return maxConnDuration; + } + + /** + * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an + * infinite lifetime. + * + * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an + * infinite lifetime. + * @deprecated Use {@link #getMaxConnDuration()}. + */ + @Deprecated + public Duration getMaxConnLifetime() { + return maxConnDuration; + } + + /** + * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an + * infinite lifetime. + * + * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an + * infinite lifetime. + * @deprecated Use {@link #getMaxConnLifetime()}. + */ + @Deprecated + public long getMaxConnLifetimeMillis() { + return maxConnDuration.toMillis(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * This method is protected but can only be implemented in this package because PooledConnectionAndInfo is a package + * private type. + * + * @param userName The user name. + * @param userPassword The user password. + * @return Matching PooledConnectionAndInfo. + * @throws SQLException Connection or registration failure. + */ + protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword) + throws SQLException; + + /** + * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller. + * If specified, this query MUST be an SQL SELECT statement that returns at least one row. If not + * specified, {@link Connection#isValid(int)} will be used to validate connections. + * + * @return The SQL query that will be used to validate connections from this pool before returning them to the + * caller. + */ + public String getValidationQuery() { + return this.validationQuery; + } + + /** + * Returns the timeout in seconds before the validation query fails. + * + * @return The timeout in seconds before the validation query fails. + * @deprecated Use {@link #getValidationQueryTimeoutDuration()}. + */ + @Deprecated + public int getValidationQueryTimeout() { + return (int) validationQueryTimeoutDuration.getSeconds(); + } + + /** + * Returns the timeout Duration before the validation query fails. + * + * @return The timeout Duration before the validation query fails. + */ + public Duration getValidationQueryTimeoutDuration() { + return validationQueryTimeoutDuration; + } + + /** + * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value + * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which + * will use the default value for the drive. + * + * @return value of defaultAutoCommit. + */ + public Boolean isDefaultAutoCommit() { + return defaultAutoCommit; + } + + /** + * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value + * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which + * will use the default value for the drive. + * + * @return value of defaultReadOnly. + */ + public Boolean isDefaultReadOnly() { + return defaultReadOnly; + } + + /** + * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from + * this pool before returning them to the caller. + * + * @return true if a rollback will be issued after executing the validation query + */ + public boolean isRollbackAfterValidation() { + return this.rollbackAfterValidation; + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + return iface.isInstance(this); + } + + /** + * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the + * data source. + * + * @param dataSource + * Value to assign to connectionPoolDataSource. + */ + public void setConnectionPoolDataSource(final ConnectionPoolDataSource dataSource) { + assertInitializationAllowed(); + if (dataSourceName != null) { + throw new IllegalStateException("Cannot set the DataSource, if JNDI is used."); + } + if (this.dataSource != null) { + throw new IllegalStateException("The CPDS has already been set. It cannot be altered."); + } + this.dataSource = dataSource; + instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this); + } + + /** + * Sets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source + * from a JNDI service provider. + * + * @param dataSourceName + * Value to assign to dataSourceName. + */ + public void setDataSourceName(final String dataSourceName) { + assertInitializationAllowed(); + if (dataSource != null) { + throw new IllegalStateException("Cannot set the JNDI name for the DataSource, if already " + + "set using setConnectionPoolDataSource."); + } + if (this.dataSourceName != null) { + throw new IllegalStateException("The DataSourceName has already been set. " + "It cannot be altered."); + } + this.dataSourceName = dataSourceName; + instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this); + } + + /** + * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value + * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which + * will use the default value for the drive. + * + * @param defaultAutoCommit + * Value to assign to defaultAutoCommit. + */ + public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { + assertInitializationAllowed(); + this.defaultAutoCommit = defaultAutoCommit; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool. + * + * @param blockWhenExhausted + * The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user + * pool. + */ + public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) { + assertInitializationAllowed(); + this.defaultBlockWhenExhausted = blockWhenExhausted; + } + + /** + * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. + * + * @param defaultDurationBetweenEvictionRuns The default value for + * {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool. + * @since 2.10.0 + */ + public void setDefaultDurationBetweenEvictionRuns(final Duration defaultDurationBetweenEvictionRuns) { + assertInitializationAllowed(); + this.defaultDurationBetweenEvictionRuns = defaultDurationBetweenEvictionRuns; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user + * pool. + * + * @param evictionPolicyClassName + * The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per + * user pool. + */ + public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) { + assertInitializationAllowed(); + this.defaultEvictionPolicyClassName = evictionPolicyClassName; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. + * + * @param lifo + * The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool. + */ + public void setDefaultLifo(final boolean lifo) { + assertInitializationAllowed(); + this.defaultLifo = lifo; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. + * + * @param maxIdle + * The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool. + */ + public void setDefaultMaxIdle(final int maxIdle) { + assertInitializationAllowed(); + this.defaultMaxIdle = maxIdle; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. + * + * @param maxTotal + * The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool. + */ + public void setDefaultMaxTotal(final int maxTotal) { + assertInitializationAllowed(); + this.defaultMaxTotal = maxTotal; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * + * @param maxWaitMillis + * The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool. + * @since 2.9.0 + */ + public void setDefaultMaxWait(final Duration maxWaitMillis) { + assertInitializationAllowed(); + this.defaultMaxWaitDuration = maxWaitMillis; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool. + * + * @param maxWaitMillis + * The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool. + * @deprecated Use {@link #setDefaultMaxWait(Duration)}. + */ + @Deprecated + public void setDefaultMaxWaitMillis(final long maxWaitMillis) { + setDefaultMaxWait(Duration.ofMillis(maxWaitMillis)); + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user + * pool. + * + * @param defaultMinEvictableIdleDuration + * The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each + * per user pool. + * @since 2.10.0 + */ + public void setDefaultMinEvictableIdle(final Duration defaultMinEvictableIdleDuration) { + assertInitializationAllowed(); + this.defaultMinEvictableIdleDuration = defaultMinEvictableIdleDuration; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user + * pool. + * + * @param minEvictableIdleTimeMillis + * The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each + * per user pool. + * @deprecated Use {@link #setDefaultMinEvictableIdle(Duration)}. + */ + @Deprecated + public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) { + assertInitializationAllowed(); + this.defaultMinEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis); + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. + * + * @param minIdle + * The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool. + */ + public void setDefaultMinIdle(final int minIdle) { + assertInitializationAllowed(); + this.defaultMinIdle = minIdle; + } + + /** + * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user + * pool. + * + * @param numTestsPerEvictionRun + * The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per + * user pool. + */ + public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { + assertInitializationAllowed(); + this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun; + } + + /** + * Sets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value + * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which + * will use the default value for the drive. + * + * @param defaultReadOnly + * Value to assign to defaultReadOnly. + */ + public void setDefaultReadOnly(final Boolean defaultReadOnly) { + assertInitializationAllowed(); + this.defaultReadOnly = defaultReadOnly; + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * + * @param defaultSoftMinEvictableIdleDuration + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * @since 2.10.0 + */ + public void setDefaultSoftMinEvictableIdle(final Duration defaultSoftMinEvictableIdleDuration) { + assertInitializationAllowed(); + this.defaultSoftMinEvictableIdleDuration = defaultSoftMinEvictableIdleDuration; + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * + * @param softMinEvictableIdleTimeMillis + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool. + * @deprecated Use {@link #setDefaultSoftMinEvictableIdle(Duration)}. + */ + @Deprecated + public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) { + assertInitializationAllowed(); + this.defaultSoftMinEvictableIdleDuration = Duration.ofMillis(softMinEvictableIdleTimeMillis); + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnBorrow()} for each per user pool. + * + * @param testOnBorrow + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnBorrow()} for each per user pool. + */ + public void setDefaultTestOnBorrow(final boolean testOnBorrow) { + assertInitializationAllowed(); + this.defaultTestOnBorrow = testOnBorrow; + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnCreate()} for each per user pool. + * + * @param testOnCreate + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnCreate()} for each per user pool. + */ + public void setDefaultTestOnCreate(final boolean testOnCreate) { + assertInitializationAllowed(); + this.defaultTestOnCreate = testOnCreate; + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnReturn()} for each per user pool. + * + * @param testOnReturn + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestOnReturn()} for each per user pool. + */ + public void setDefaultTestOnReturn(final boolean testOnReturn) { + assertInitializationAllowed(); + this.defaultTestOnReturn = testOnReturn; + } + + /** + * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestWhileIdle()} for each per user pool. + * + * @param testWhileIdle + * The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool + * GenericObjectPool#getTestWhileIdle()} for each per user pool. + */ + public void setDefaultTestWhileIdle(final boolean testWhileIdle) { + assertInitializationAllowed(); + this.defaultTestWhileIdle = testWhileIdle; + } + + /** + * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. + * + * @param timeBetweenEvictionRunsMillis The default value for + * {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool. + * @deprecated Use {@link #setDefaultDurationBetweenEvictionRuns(Duration)}. + */ + @Deprecated + public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { + assertInitializationAllowed(); + this.defaultDurationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis); + } + + /** + * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool. + * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC + * driver dependent. + * + * @param defaultTransactionIsolation + * Value to assign to defaultTransactionIsolation + */ + public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { + assertInitializationAllowed(); + switch (defaultTransactionIsolation) { + case Connection.TRANSACTION_NONE: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + break; + default: + throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION); + } + this.defaultTransactionIsolation = defaultTransactionIsolation; + } + + /** + * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the + * datasource. It serves no internal purpose. + * + * @param description + * Value to assign to description. + */ + public void setDescription(final String description) { + this.description = description; + } + + /** + * Sets the JNDI environment to be used when instantiating a JNDI InitialContext. This InitialContext is used to + * locate the back end ConnectionPoolDataSource. + * + * @param properties + * the JNDI environment property to set which will overwrite any current settings + */ + void setJndiEnvironment(final Properties properties) { + if (jndiEnvironment == null) { + jndiEnvironment = new Properties(); + } else { + jndiEnvironment.clear(); + } + jndiEnvironment.putAll(properties); + } + + /** + * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This + * InitialContext is used to locate the back end ConnectionPoolDataSource. + * + * @param key + * the JNDI environment property to set. + * @param value + * the value assigned to specified JNDI environment property. + */ + public void setJndiEnvironment(final String key, final String value) { + if (jndiEnvironment == null) { + jndiEnvironment = new Properties(); + } + jndiEnvironment.setProperty(key, value); + } + + /** + * Sets the value of loginTimeout. + * + * @param loginTimeout + * Value to assign to loginTimeout. + * @since 2.10.0 + */ + public void setLoginTimeout(final Duration loginTimeout) { + this.loginTimeoutDuration = loginTimeout; + } + + /** + * Sets the value of loginTimeout. + * + * @param loginTimeout + * Value to assign to loginTimeout. + * @deprecated Use {@link #setLoginTimeout(Duration)}. + */ + @Deprecated + @Override + public void setLoginTimeout(final int loginTimeout) { + this.loginTimeoutDuration = Duration.ofSeconds(loginTimeout); + } + + /** + * Sets the value of logWriter. + * + * @param logWriter + * Value to assign to logWriter. + */ + @Override + public void setLogWriter(final PrintWriter logWriter) { + this.logWriter = logWriter; + } + + /** + *

+ * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime. + *

+ *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first time one of the following methods is + * invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()}, + * {@link #getLogWriter()}. + *

+ * + * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime. + * @since 2.9.0 + */ + public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) { + this.maxConnDuration = maxConnLifetimeMillis; + } + + /** + *

+ * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime. + *

+ *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first time one of the following methods is + * invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()}, + * {@link #getLogWriter()}. + *

+ * + * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime. + * @deprecated Use {@link #setMaxConnLifetime(Duration)}. + */ + @Deprecated + public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { + setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis)); + } + + /** + * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from + * this pool before returning them to the caller. Default behavior is NOT to issue a rollback. The setting will only + * have an effect if a validation query is set + * + * @param rollbackAfterValidation + * new property value + */ + public void setRollbackAfterValidation(final boolean rollbackAfterValidation) { + assertInitializationAllowed(); + this.rollbackAfterValidation = rollbackAfterValidation; + } + + protected abstract void setupDefaults(Connection connection, String userName) throws SQLException; + + /** + * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller. + * If specified, this query MUST be an SQL SELECT statement that returns at least one row. If not + * specified, connections will be validated using {@link Connection#isValid(int)}. + * + * @param validationQuery + * The SQL query that will be used to validate connections from this pool before returning them to the + * caller. + */ + public void setValidationQuery(final String validationQuery) { + assertInitializationAllowed(); + this.validationQuery = validationQuery; + } + + /** + * Sets the timeout duration before the validation query fails. + * + * @param validationQueryTimeoutDuration + * The new timeout duration. + */ + public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) { + this.validationQueryTimeoutDuration = validationQueryTimeoutDuration; + } + + /** + * Sets the timeout in seconds before the validation query fails. + * + * @param validationQueryTimeoutSeconds + * The new timeout in seconds + * @deprecated Use {@link #setValidationQueryTimeout(Duration)}. + */ + @Deprecated + public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { + this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds); + } + + protected ConnectionPoolDataSource testCPDS(final String userName, final String userPassword) + throws javax.naming.NamingException, SQLException { + // The source of physical database connections + ConnectionPoolDataSource cpds = this.dataSource; + if (cpds == null) { + Context ctx = null; + if (jndiEnvironment == null) { + ctx = new InitialContext(); + } else { + ctx = new InitialContext(jndiEnvironment); + } + final Object ds = ctx.lookup(dataSourceName); + if (!(ds instanceof ConnectionPoolDataSource)) { + throw new SQLException("Illegal configuration: " + "DataSource " + dataSourceName + " (" + + ds.getClass().getName() + ")" + " doesn't implement javax.sql.ConnectionPoolDataSource"); + } + cpds = (ConnectionPoolDataSource) ds; + } + + // try to get a connection with the supplied userName/password + PooledConnection conn = null; + try { + if (userName != null) { + conn = cpds.getPooledConnection(userName, userPassword); + } else { + conn = cpds.getPooledConnection(); + } + if (conn == null) { + throw new SQLException("Cannot connect using the supplied userName/password"); + } + } finally { + if (conn != null) { + try { + conn.close(); + } catch (final SQLException ignored) { + // at least we could connect + } + } + } + return cpds; + } + + /** + * @since 2.6.0 + */ + @Override + public synchronized String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + builder.append('['); + toStringFields(builder); + builder.append(']'); + return builder.toString(); + } + + protected void toStringFields(final StringBuilder builder) { + builder.append("getConnectionCalled="); + builder.append(getConnectionCalled); + builder.append(", dataSource="); + builder.append(dataSource); + builder.append(", dataSourceName="); + builder.append(dataSourceName); + builder.append(", description="); + builder.append(description); + builder.append(", jndiEnvironment="); + builder.append(jndiEnvironment); + builder.append(", loginTimeoutDuration="); + builder.append(loginTimeoutDuration); + builder.append(", logWriter="); + builder.append(logWriter); + builder.append(", instanceKey="); + builder.append(instanceKey); + builder.append(", defaultBlockWhenExhausted="); + builder.append(defaultBlockWhenExhausted); + builder.append(", defaultEvictionPolicyClassName="); + builder.append(defaultEvictionPolicyClassName); + builder.append(", defaultLifo="); + builder.append(defaultLifo); + builder.append(", defaultMaxIdle="); + builder.append(defaultMaxIdle); + builder.append(", defaultMaxTotal="); + builder.append(defaultMaxTotal); + builder.append(", defaultMaxWaitDuration="); + builder.append(defaultMaxWaitDuration); + builder.append(", defaultMinEvictableIdleDuration="); + builder.append(defaultMinEvictableIdleDuration); + builder.append(", defaultMinIdle="); + builder.append(defaultMinIdle); + builder.append(", defaultNumTestsPerEvictionRun="); + builder.append(defaultNumTestsPerEvictionRun); + builder.append(", defaultSoftMinEvictableIdleDuration="); + builder.append(defaultSoftMinEvictableIdleDuration); + builder.append(", defaultTestOnCreate="); + builder.append(defaultTestOnCreate); + builder.append(", defaultTestOnBorrow="); + builder.append(defaultTestOnBorrow); + builder.append(", defaultTestOnReturn="); + builder.append(defaultTestOnReturn); + builder.append(", defaultTestWhileIdle="); + builder.append(defaultTestWhileIdle); + builder.append(", defaultDurationBetweenEvictionRuns="); + builder.append(defaultDurationBetweenEvictionRuns); + builder.append(", validationQuery="); + builder.append(validationQuery); + builder.append(", validationQueryTimeoutDuration="); + builder.append(validationQueryTimeoutDuration); + builder.append(", rollbackAfterValidation="); + builder.append(rollbackAfterValidation); + builder.append(", maxConnDuration="); + builder.append(maxConnDuration); + builder.append(", defaultAutoCommit="); + builder.append(defaultAutoCommit); + builder.append(", defaultTransactionIsolation="); + builder.append(defaultTransactionIsolation); + builder.append(", defaultReadOnly="); + builder.append(defaultReadOnly); + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(final Class iface) throws SQLException { + if (isWrapperFor(iface)) { + return (T) this; + } + throw new SQLException(this + " is not a wrapper for " + iface); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java index 376a3e05cd..740a85e5b2 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java @@ -1,355 +1,355 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -import javax.naming.Context; -import javax.naming.Name; -import javax.naming.RefAddr; -import javax.naming.Reference; -import javax.naming.spi.ObjectFactory; - -import org.apache.commons.dbcp2.ListException; -import org.apache.commons.dbcp2.Utils; - -/** - * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s - * - * @since 2.0 - */ -abstract class InstanceKeyDataSourceFactory implements ObjectFactory { - - private static final Map INSTANCE_MAP = new ConcurrentHashMap<>(); - - /** - * Closes all pools associated with this class. - * - * @throws ListException - * a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()} - * @see InstanceKeyDataSource#close() - * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by - * {@link InstanceKeyDataSource#close()}. - */ - public static void closeAll() throws ListException { - // Get iterator to loop over all instances of this data source. - final List exceptionList = new ArrayList<>(INSTANCE_MAP.size()); - INSTANCE_MAP.entrySet().forEach(entry -> { - // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close(). - if (entry != null) { - @SuppressWarnings("resource") - final InstanceKeyDataSource value = entry.getValue(); - Utils.close(value, exceptionList::add); - } - }); - INSTANCE_MAP.clear(); - if (!exceptionList.isEmpty()) { - throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList); - } - } - - /** - * Deserializes the provided byte array to create an object. - * - * @param data - * Data to deserialize to create the configuration parameter. - * - * @return The Object created by deserializing the data. - * @throws ClassNotFoundException - * If a class cannot be found during the deserialization of a configuration parameter. - * @throws IOException - * If an I/O error occurs during the deserialization of a configuration parameter. - */ - protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException { - ObjectInputStream in = null; - try { - in = new ObjectInputStream(new ByteArrayInputStream(data)); - return in.readObject(); - } finally { - Utils.closeQuietly(in); - } - } - - static synchronized String registerNewInstance(final InstanceKeyDataSource ds) { - int max = 0; - for (final String s : INSTANCE_MAP.keySet()) { - if (s != null) { - try { - max = Math.max(max, Integer.parseInt(s)); - } catch (final NumberFormatException ignored) { - // no sweat, ignore those keys - } - } - } - final String instanceKey = String.valueOf(max + 1); - // Put a placeholder here for now, so other instances will not - // take our key. We will replace with a pool when ready. - INSTANCE_MAP.put(instanceKey, ds); - return instanceKey; - } - - static void removeInstance(final String key) { - if (key != null) { - INSTANCE_MAP.remove(key); - } - } - - private Boolean booleanValueOf(RefAddr refAddr) { - return Boolean.valueOf(toString(refAddr)); - } - - /** - * Creates an instance of the subclass and sets any properties contained in the Reference. - * - * @param ref - * The properties to be set on the created DataSource - * - * @return A configured DataSource of the appropriate type. - * @throws ClassNotFoundException - * If a class cannot be found during the deserialization of a configuration parameter. - * @throws IOException - * If an I/O error occurs during the deserialization of a configuration parameter. - */ - protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException; - - /** - * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource - */ - @Override - public Object getObjectInstance(final Object refObj, final Name name, final Context context, - final Hashtable env) throws IOException, ClassNotFoundException { - // The spec says to return null if we can't create an instance - // of the reference - Object obj = null; - if (refObj instanceof Reference) { - final Reference ref = (Reference) refObj; - if (isCorrectClass(ref.getClassName())) { - final RefAddr refAddr = ref.get("instanceKey"); - if (hasContent(refAddr)) { - // object was bound to JNDI via Referenceable API. - obj = INSTANCE_MAP.get(refAddr.getContent()); - } else { - // Tomcat JNDI creates a Reference out of server.xml - // configuration and passes it to an - // instance of the factory given in server.xml. - String key = null; - if (name != null) { - key = name.toString(); - obj = INSTANCE_MAP.get(key); - } - if (obj == null) { - final InstanceKeyDataSource ds = getNewInstance(ref); - setCommonProperties(ref, ds); - obj = ds; - if (key != null) { - INSTANCE_MAP.put(key, ds); - } - } - } - } - } - return obj; - } - - private boolean hasContent(final RefAddr refAddr) { - return refAddr != null && refAddr.getContent() != null; - } - - /** - * Tests if className is the value returned from getClass().getName().toString(). - * - * @param className - * The class name to test. - * - * @return true if and only if className is the value returned from getClass().getName().toString() - */ - protected abstract boolean isCorrectClass(String className); - - boolean parseBoolean(final RefAddr refAddr) { - return Boolean.parseBoolean(toString(refAddr)); - } - - int parseInt(final RefAddr refAddr) { - return Integer.parseInt(toString(refAddr)); - } - - long parseLong(final RefAddr refAddr) { - return Long.parseLong(toString(refAddr)); - } - - private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds) - throws IOException, ClassNotFoundException { - - RefAddr refAddr = ref.get("dataSourceName"); - if (hasContent(refAddr)) { - ikds.setDataSourceName(toString(refAddr)); - } - - refAddr = ref.get("description"); - if (hasContent(refAddr)) { - ikds.setDescription(toString(refAddr)); - } - - refAddr = ref.get("jndiEnvironment"); - if (hasContent(refAddr)) { - final byte[] serialized = (byte[]) refAddr.getContent(); - ikds.setJndiEnvironment((Properties) deserialize(serialized)); - } - - refAddr = ref.get("loginTimeout"); - if (hasContent(refAddr)) { - ikds.setLoginTimeout(toDurationFromSeconds(refAddr)); - } - - // Pool properties - refAddr = ref.get("blockWhenExhausted"); - if (hasContent(refAddr)) { - ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr)); - } - - refAddr = ref.get("evictionPolicyClassName"); - if (hasContent(refAddr)) { - ikds.setDefaultEvictionPolicyClassName(toString(refAddr)); - } - - // Pool properties - refAddr = ref.get("lifo"); - if (hasContent(refAddr)) { - ikds.setDefaultLifo(parseBoolean(refAddr)); - } - - refAddr = ref.get("maxIdlePerKey"); - if (hasContent(refAddr)) { - ikds.setDefaultMaxIdle(parseInt(refAddr)); - } - - refAddr = ref.get("maxTotalPerKey"); - if (hasContent(refAddr)) { - ikds.setDefaultMaxTotal(parseInt(refAddr)); - } - - refAddr = ref.get("maxWaitMillis"); - if (hasContent(refAddr)) { - ikds.setDefaultMaxWait(toDurationFromMillis(refAddr)); - } - - refAddr = ref.get("minEvictableIdleTimeMillis"); - if (hasContent(refAddr)) { - ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr)); - } - - refAddr = ref.get("minIdlePerKey"); - if (hasContent(refAddr)) { - ikds.setDefaultMinIdle(parseInt(refAddr)); - } - - refAddr = ref.get("numTestsPerEvictionRun"); - if (hasContent(refAddr)) { - ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr)); - } - - refAddr = ref.get("softMinEvictableIdleTimeMillis"); - if (hasContent(refAddr)) { - ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr)); - } - - refAddr = ref.get("testOnCreate"); - if (hasContent(refAddr)) { - ikds.setDefaultTestOnCreate(parseBoolean(refAddr)); - } - - refAddr = ref.get("testOnBorrow"); - if (hasContent(refAddr)) { - ikds.setDefaultTestOnBorrow(parseBoolean(refAddr)); - } - - refAddr = ref.get("testOnReturn"); - if (hasContent(refAddr)) { - ikds.setDefaultTestOnReturn(parseBoolean(refAddr)); - } - - refAddr = ref.get("testWhileIdle"); - if (hasContent(refAddr)) { - ikds.setDefaultTestWhileIdle(parseBoolean(refAddr)); - } - - refAddr = ref.get("timeBetweenEvictionRunsMillis"); - if (hasContent(refAddr)) { - ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr)); - } - - // Connection factory properties - - refAddr = ref.get("validationQuery"); - if (hasContent(refAddr)) { - ikds.setValidationQuery(toString(refAddr)); - } - - refAddr = ref.get("validationQueryTimeout"); - if (hasContent(refAddr)) { - ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr)); - } - - refAddr = ref.get("rollbackAfterValidation"); - if (hasContent(refAddr)) { - ikds.setRollbackAfterValidation(parseBoolean(refAddr)); - } - - refAddr = ref.get("maxConnLifetimeMillis"); - if (hasContent(refAddr)) { - ikds.setMaxConnLifetime(toDurationFromMillis(refAddr)); - } - - // Connection properties - - refAddr = ref.get("defaultAutoCommit"); - if (hasContent(refAddr)) { - ikds.setDefaultAutoCommit(booleanValueOf(refAddr)); - } - - refAddr = ref.get("defaultTransactionIsolation"); - if (hasContent(refAddr)) { - ikds.setDefaultTransactionIsolation(parseInt(refAddr)); - } - - refAddr = ref.get("defaultReadOnly"); - if (hasContent(refAddr)) { - ikds.setDefaultReadOnly(booleanValueOf(refAddr)); - } - } - - private Duration toDurationFromMillis(RefAddr refAddr) { - return Duration.ofMillis(parseLong(refAddr)); - } - - private Duration toDurationFromSeconds(RefAddr refAddr) { - return Duration.ofSeconds(parseInt(refAddr)); - } - - String toString(final RefAddr refAddr) { - return refAddr.getContent().toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; + +import org.apache.commons.dbcp2.ListException; +import org.apache.commons.dbcp2.Utils; + +/** + * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s + * + * @since 2.0 + */ +abstract class InstanceKeyDataSourceFactory implements ObjectFactory { + + private static final Map INSTANCE_MAP = new ConcurrentHashMap<>(); + + /** + * Closes all pools associated with this class. + * + * @throws ListException + * a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()} + * @see InstanceKeyDataSource#close() + * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by + * {@link InstanceKeyDataSource#close()}. + */ + public static void closeAll() throws ListException { + // Get iterator to loop over all instances of this data source. + final List exceptionList = new ArrayList<>(INSTANCE_MAP.size()); + INSTANCE_MAP.entrySet().forEach(entry -> { + // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close(). + if (entry != null) { + @SuppressWarnings("resource") + final InstanceKeyDataSource value = entry.getValue(); + Utils.close(value, exceptionList::add); + } + }); + INSTANCE_MAP.clear(); + if (!exceptionList.isEmpty()) { + throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList); + } + } + + /** + * Deserializes the provided byte array to create an object. + * + * @param data + * Data to deserialize to create the configuration parameter. + * + * @return The Object created by deserializing the data. + * @throws ClassNotFoundException + * If a class cannot be found during the deserialization of a configuration parameter. + * @throws IOException + * If an I/O error occurs during the deserialization of a configuration parameter. + */ + protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException { + ObjectInputStream in = null; + try { + in = new ObjectInputStream(new ByteArrayInputStream(data)); + return in.readObject(); + } finally { + Utils.closeQuietly(in); + } + } + + static synchronized String registerNewInstance(final InstanceKeyDataSource ds) { + int max = 0; + for (final String s : INSTANCE_MAP.keySet()) { + if (s != null) { + try { + max = Math.max(max, Integer.parseInt(s)); + } catch (final NumberFormatException ignored) { + // no sweat, ignore those keys + } + } + } + final String instanceKey = String.valueOf(max + 1); + // Put a placeholder here for now, so other instances will not + // take our key. We will replace with a pool when ready. + INSTANCE_MAP.put(instanceKey, ds); + return instanceKey; + } + + static void removeInstance(final String key) { + if (key != null) { + INSTANCE_MAP.remove(key); + } + } + + private Boolean booleanValueOf(RefAddr refAddr) { + return Boolean.valueOf(toString(refAddr)); + } + + /** + * Creates an instance of the subclass and sets any properties contained in the Reference. + * + * @param ref + * The properties to be set on the created DataSource + * + * @return A configured DataSource of the appropriate type. + * @throws ClassNotFoundException + * If a class cannot be found during the deserialization of a configuration parameter. + * @throws IOException + * If an I/O error occurs during the deserialization of a configuration parameter. + */ + protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException; + + /** + * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource + */ + @Override + public Object getObjectInstance(final Object refObj, final Name name, final Context context, + final Hashtable env) throws IOException, ClassNotFoundException { + // The spec says to return null if we can't create an instance + // of the reference + Object obj = null; + if (refObj instanceof Reference) { + final Reference ref = (Reference) refObj; + if (isCorrectClass(ref.getClassName())) { + final RefAddr refAddr = ref.get("instanceKey"); + if (hasContent(refAddr)) { + // object was bound to JNDI via Referenceable API. + obj = INSTANCE_MAP.get(refAddr.getContent()); + } else { + // Tomcat JNDI creates a Reference out of server.xml + // configuration and passes it to an + // instance of the factory given in server.xml. + String key = null; + if (name != null) { + key = name.toString(); + obj = INSTANCE_MAP.get(key); + } + if (obj == null) { + final InstanceKeyDataSource ds = getNewInstance(ref); + setCommonProperties(ref, ds); + obj = ds; + if (key != null) { + INSTANCE_MAP.put(key, ds); + } + } + } + } + } + return obj; + } + + private boolean hasContent(final RefAddr refAddr) { + return refAddr != null && refAddr.getContent() != null; + } + + /** + * Tests if className is the value returned from getClass().getName().toString(). + * + * @param className + * The class name to test. + * + * @return true if and only if className is the value returned from getClass().getName().toString() + */ + protected abstract boolean isCorrectClass(String className); + + boolean parseBoolean(final RefAddr refAddr) { + return Boolean.parseBoolean(toString(refAddr)); + } + + int parseInt(final RefAddr refAddr) { + return Integer.parseInt(toString(refAddr)); + } + + long parseLong(final RefAddr refAddr) { + return Long.parseLong(toString(refAddr)); + } + + private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds) + throws IOException, ClassNotFoundException { + + RefAddr refAddr = ref.get("dataSourceName"); + if (hasContent(refAddr)) { + ikds.setDataSourceName(toString(refAddr)); + } + + refAddr = ref.get("description"); + if (hasContent(refAddr)) { + ikds.setDescription(toString(refAddr)); + } + + refAddr = ref.get("jndiEnvironment"); + if (hasContent(refAddr)) { + final byte[] serialized = (byte[]) refAddr.getContent(); + ikds.setJndiEnvironment((Properties) deserialize(serialized)); + } + + refAddr = ref.get("loginTimeout"); + if (hasContent(refAddr)) { + ikds.setLoginTimeout(toDurationFromSeconds(refAddr)); + } + + // Pool properties + refAddr = ref.get("blockWhenExhausted"); + if (hasContent(refAddr)) { + ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr)); + } + + refAddr = ref.get("evictionPolicyClassName"); + if (hasContent(refAddr)) { + ikds.setDefaultEvictionPolicyClassName(toString(refAddr)); + } + + // Pool properties + refAddr = ref.get("lifo"); + if (hasContent(refAddr)) { + ikds.setDefaultLifo(parseBoolean(refAddr)); + } + + refAddr = ref.get("maxIdlePerKey"); + if (hasContent(refAddr)) { + ikds.setDefaultMaxIdle(parseInt(refAddr)); + } + + refAddr = ref.get("maxTotalPerKey"); + if (hasContent(refAddr)) { + ikds.setDefaultMaxTotal(parseInt(refAddr)); + } + + refAddr = ref.get("maxWaitMillis"); + if (hasContent(refAddr)) { + ikds.setDefaultMaxWait(toDurationFromMillis(refAddr)); + } + + refAddr = ref.get("minEvictableIdleTimeMillis"); + if (hasContent(refAddr)) { + ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr)); + } + + refAddr = ref.get("minIdlePerKey"); + if (hasContent(refAddr)) { + ikds.setDefaultMinIdle(parseInt(refAddr)); + } + + refAddr = ref.get("numTestsPerEvictionRun"); + if (hasContent(refAddr)) { + ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr)); + } + + refAddr = ref.get("softMinEvictableIdleTimeMillis"); + if (hasContent(refAddr)) { + ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr)); + } + + refAddr = ref.get("testOnCreate"); + if (hasContent(refAddr)) { + ikds.setDefaultTestOnCreate(parseBoolean(refAddr)); + } + + refAddr = ref.get("testOnBorrow"); + if (hasContent(refAddr)) { + ikds.setDefaultTestOnBorrow(parseBoolean(refAddr)); + } + + refAddr = ref.get("testOnReturn"); + if (hasContent(refAddr)) { + ikds.setDefaultTestOnReturn(parseBoolean(refAddr)); + } + + refAddr = ref.get("testWhileIdle"); + if (hasContent(refAddr)) { + ikds.setDefaultTestWhileIdle(parseBoolean(refAddr)); + } + + refAddr = ref.get("timeBetweenEvictionRunsMillis"); + if (hasContent(refAddr)) { + ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr)); + } + + // Connection factory properties + + refAddr = ref.get("validationQuery"); + if (hasContent(refAddr)) { + ikds.setValidationQuery(toString(refAddr)); + } + + refAddr = ref.get("validationQueryTimeout"); + if (hasContent(refAddr)) { + ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr)); + } + + refAddr = ref.get("rollbackAfterValidation"); + if (hasContent(refAddr)) { + ikds.setRollbackAfterValidation(parseBoolean(refAddr)); + } + + refAddr = ref.get("maxConnLifetimeMillis"); + if (hasContent(refAddr)) { + ikds.setMaxConnLifetime(toDurationFromMillis(refAddr)); + } + + // Connection properties + + refAddr = ref.get("defaultAutoCommit"); + if (hasContent(refAddr)) { + ikds.setDefaultAutoCommit(booleanValueOf(refAddr)); + } + + refAddr = ref.get("defaultTransactionIsolation"); + if (hasContent(refAddr)) { + ikds.setDefaultTransactionIsolation(parseInt(refAddr)); + } + + refAddr = ref.get("defaultReadOnly"); + if (hasContent(refAddr)) { + ikds.setDefaultReadOnly(booleanValueOf(refAddr)); + } + } + + private Duration toDurationFromMillis(RefAddr refAddr) { + return Duration.ofMillis(parseLong(refAddr)); + } + + private Duration toDurationFromSeconds(RefAddr refAddr) { + return Duration.ofSeconds(parseInt(refAddr)); + } + + String toString(final RefAddr refAddr) { + return refAddr.getContent().toString(); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java index bd8b89fd6f..df68c43f2c 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java @@ -1,244 +1,244 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Duration; - -import javax.sql.ConnectionEvent; -import javax.sql.ConnectionEventListener; -import javax.sql.ConnectionPoolDataSource; -import javax.sql.PooledConnection; - -import org.apache.commons.dbcp2.PoolableConnection; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.KeyedPooledObjectFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultPooledObject; - -/** - * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s. - * - * @since 2.0 - */ -final class KeyedCPDSConnectionFactory extends AbstractConnectionFactory - implements KeyedPooledObjectFactory, ConnectionEventListener, PooledConnectionManager { - - private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but " - + "I have no record of the underlying PooledConnection."; - - private KeyedObjectPool pool; - - /** - * Creates a new {@code KeyedPoolableConnectionFactory}. - * - * @param cpds - * the ConnectionPoolDataSource from which to obtain PooledConnections - * @param validationQuery - * a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one - * row. May be {@code null} in which case3 {@link Connection#isValid(int)} will be used to validate - * connections. - * @param validationQueryTimeoutDuration - * The Duration to allow for the validation query to complete - * @param rollbackAfterValidation - * whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s. - * @since 2.10.0 - */ - public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration, - final boolean rollbackAfterValidation) { - super(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation); - } - - @Override - public void activateObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { - validateLifetime(pooledObject); - } - - /** - * This implementation does not fully close the KeyedObjectPool, as this would affect all users. Instead, it clears - * the pool associated with the given user. This method is not currently used. - */ - @Override - public void closePool(final String userName) throws SQLException { - try { - pool.clear(new UserPassKey(userName)); - } catch (final Exception ex) { - throw new SQLException("Error closing connection pool", ex); - } - } - - /** - * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the - * user calls the close() method of this connection object. What we need to do here is to release this - * PooledConnection from our pool... - */ - @Override - public void connectionClosed(final ConnectionEvent event) { - final PooledConnection pc = (PooledConnection) event.getSource(); - // if this event occurred because we were validating, or if this - // connection has been marked for removal, ignore it - // otherwise return the connection to the pool. - if (!validatingSet.contains(pc)) { - final PooledConnectionAndInfo pci = pcMap.get(pc); - if (pci == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - try { - pool.returnObject(pci.getUserPassKey(), pci); - } catch (final Exception e) { - System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); - pc.removeConnectionEventListener(this); - try { - pool.invalidateObject(pci.getUserPassKey(), pci); - } catch (final Exception e3) { - System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); - e3.printStackTrace(); - } - } - } - } - - /** - * If a fatal error occurs, close the underlying physical connection so as not to be returned in the future - */ - @Override - public void connectionErrorOccurred(final ConnectionEvent event) { - final PooledConnection pc = (PooledConnection) event.getSource(); - if (null != event.getSQLException()) { - System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); - } - pc.removeConnectionEventListener(this); - - final PooledConnectionAndInfo info = pcMap.get(pc); - if (info == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - try { - pool.invalidateObject(info.getUserPassKey(), info); - } catch (final Exception e) { - System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); - e.printStackTrace(); - } - } - - /** - * Closes the PooledConnection and stops listening for events from it. - */ - @Override - public void destroyObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { - final PooledConnection pooledConnection = pooledObject.getObject().getPooledConnection(); - pooledConnection.removeConnectionEventListener(this); - pcMap.remove(pooledConnection); - pooledConnection.close(); - } - - /** - * Returns the keyed object pool used to pool connections created by this factory. - * - * @return KeyedObjectPool managing pooled connections - */ - public KeyedObjectPool getPool() { - return pool; - } - - /** - * Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory closes the connection and pool - * counters are updated appropriately. Also clears any idle instances associated with the user name that was used to - * create the PooledConnection. Connections associated with this user are not affected, and they will not be - * automatically closed on return to the pool. - */ - @Override - public void invalidate(final PooledConnection pc) throws SQLException { - final PooledConnectionAndInfo info = pcMap.get(pc); - if (info == null) { - throw new IllegalStateException(NO_KEY_MESSAGE); - } - final UserPassKey key = info.getUserPassKey(); - try { - pool.invalidateObject(key, info); // Destroy and update pool counters - pool.clear(key); // Remove any idle instances with this key - } catch (final Exception ex) { - throw new SQLException("Error invalidating connection", ex); - } - } - - /** - * Creates a new {@code PooledConnectionAndInfo} from the given {@code UserPassKey}. - * - * @param userPassKey - * {@code UserPassKey} containing user credentials - * @throws SQLException - * if the connection could not be created. - * @see org.apache.commons.pool2.KeyedPooledObjectFactory#makeObject(Object) - */ - @Override - public synchronized PooledObject makeObject(final UserPassKey userPassKey) throws SQLException { - PooledConnection pooledConnection = null; - final String userName = userPassKey.getUserName(); - final String password = userPassKey.getPassword(); - if (userName == null) { - pooledConnection = cpds.getPooledConnection(); - } else { - pooledConnection = cpds.getPooledConnection(userName, password); - } - if (pooledConnection == null) { - throw new IllegalStateException("Connection pool data source returned null from getPooledConnection"); - } - // should we add this object as a listener or the pool. - // consider the validateObject method in decision - pooledConnection.addConnectionEventListener(this); - final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pooledConnection, userPassKey); - pcMap.put(pooledConnection, pci); - return new DefaultPooledObject<>(pci); - } - - @Override - public void passivateObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { - validateLifetime(pooledObject); - } - - /** - * Does nothing. This factory does not cache user credentials. - */ - @Override - public void setPassword(final String password) { - // Does nothing. This factory does not cache user credentials. - } - - public void setPool(final KeyedObjectPool pool) { - this.pool = pool; - } - - /** - * Validates a pooled connection. - *

- * A query validation timeout greater than 0 and less than 1 second is converted to 1 second. - *

- * - * @param ignored - * ignored - * @param pooledObject - * wrapped {@code PooledConnectionAndInfo} containing the connection to validate - * @return true if validation succeeds - * @throws ArithmeticException if the query validation timeout does not fit as seconds in an int. - */ - @Override - public boolean validateObject(final UserPassKey ignored, final PooledObject pooledObject) { - return super.validateObject(pooledObject); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.PooledConnection; + +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +/** + * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s. + * + * @since 2.0 + */ +final class KeyedCPDSConnectionFactory extends AbstractConnectionFactory + implements KeyedPooledObjectFactory, ConnectionEventListener, PooledConnectionManager { + + private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but " + + "I have no record of the underlying PooledConnection."; + + private KeyedObjectPool pool; + + /** + * Creates a new {@code KeyedPoolableConnectionFactory}. + * + * @param cpds + * the ConnectionPoolDataSource from which to obtain PooledConnections + * @param validationQuery + * a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one + * row. May be {@code null} in which case3 {@link Connection#isValid(int)} will be used to validate + * connections. + * @param validationQueryTimeoutDuration + * The Duration to allow for the validation query to complete + * @param rollbackAfterValidation + * whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s. + * @since 2.10.0 + */ + public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration, + final boolean rollbackAfterValidation) { + super(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation); + } + + @Override + public void activateObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { + validateLifetime(pooledObject); + } + + /** + * This implementation does not fully close the KeyedObjectPool, as this would affect all users. Instead, it clears + * the pool associated with the given user. This method is not currently used. + */ + @Override + public void closePool(final String userName) throws SQLException { + try { + pool.clear(new UserPassKey(userName)); + } catch (final Exception ex) { + throw new SQLException("Error closing connection pool", ex); + } + } + + /** + * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the + * user calls the close() method of this connection object. What we need to do here is to release this + * PooledConnection from our pool... + */ + @Override + public void connectionClosed(final ConnectionEvent event) { + final PooledConnection pc = (PooledConnection) event.getSource(); + // if this event occurred because we were validating, or if this + // connection has been marked for removal, ignore it + // otherwise return the connection to the pool. + if (!validatingSet.contains(pc)) { + final PooledConnectionAndInfo pci = pcMap.get(pc); + if (pci == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + try { + pool.returnObject(pci.getUserPassKey(), pci); + } catch (final Exception e) { + System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); + pc.removeConnectionEventListener(this); + try { + pool.invalidateObject(pci.getUserPassKey(), pci); + } catch (final Exception e3) { + System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci); + e3.printStackTrace(); + } + } + } + } + + /** + * If a fatal error occurs, close the underlying physical connection so as not to be returned in the future + */ + @Override + public void connectionErrorOccurred(final ConnectionEvent event) { + final PooledConnection pc = (PooledConnection) event.getSource(); + if (null != event.getSQLException()) { + System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); + } + pc.removeConnectionEventListener(this); + + final PooledConnectionAndInfo info = pcMap.get(pc); + if (info == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + try { + pool.invalidateObject(info.getUserPassKey(), info); + } catch (final Exception e) { + System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); + e.printStackTrace(); + } + } + + /** + * Closes the PooledConnection and stops listening for events from it. + */ + @Override + public void destroyObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { + final PooledConnection pooledConnection = pooledObject.getObject().getPooledConnection(); + pooledConnection.removeConnectionEventListener(this); + pcMap.remove(pooledConnection); + pooledConnection.close(); + } + + /** + * Returns the keyed object pool used to pool connections created by this factory. + * + * @return KeyedObjectPool managing pooled connections + */ + public KeyedObjectPool getPool() { + return pool; + } + + /** + * Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory closes the connection and pool + * counters are updated appropriately. Also clears any idle instances associated with the user name that was used to + * create the PooledConnection. Connections associated with this user are not affected, and they will not be + * automatically closed on return to the pool. + */ + @Override + public void invalidate(final PooledConnection pc) throws SQLException { + final PooledConnectionAndInfo info = pcMap.get(pc); + if (info == null) { + throw new IllegalStateException(NO_KEY_MESSAGE); + } + final UserPassKey key = info.getUserPassKey(); + try { + pool.invalidateObject(key, info); // Destroy and update pool counters + pool.clear(key); // Remove any idle instances with this key + } catch (final Exception ex) { + throw new SQLException("Error invalidating connection", ex); + } + } + + /** + * Creates a new {@code PooledConnectionAndInfo} from the given {@code UserPassKey}. + * + * @param userPassKey + * {@code UserPassKey} containing user credentials + * @throws SQLException + * if the connection could not be created. + * @see org.apache.commons.pool2.KeyedPooledObjectFactory#makeObject(Object) + */ + @Override + public synchronized PooledObject makeObject(final UserPassKey userPassKey) throws SQLException { + PooledConnection pooledConnection = null; + final String userName = userPassKey.getUserName(); + final String password = userPassKey.getPassword(); + if (userName == null) { + pooledConnection = cpds.getPooledConnection(); + } else { + pooledConnection = cpds.getPooledConnection(userName, password); + } + if (pooledConnection == null) { + throw new IllegalStateException("Connection pool data source returned null from getPooledConnection"); + } + // should we add this object as a listener or the pool. + // consider the validateObject method in decision + pooledConnection.addConnectionEventListener(this); + final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pooledConnection, userPassKey); + pcMap.put(pooledConnection, pci); + return new DefaultPooledObject<>(pci); + } + + @Override + public void passivateObject(final UserPassKey ignored, final PooledObject pooledObject) throws SQLException { + validateLifetime(pooledObject); + } + + /** + * Does nothing. This factory does not cache user credentials. + */ + @Override + public void setPassword(final String password) { + // Does nothing. This factory does not cache user credentials. + } + + public void setPool(final KeyedObjectPool pool) { + this.pool = pool; + } + + /** + * Validates a pooled connection. + *

+ * A query validation timeout greater than 0 and less than 1 second is converted to 1 second. + *

+ * + * @param ignored + * ignored + * @param pooledObject + * wrapped {@code PooledConnectionAndInfo} containing the connection to validate + * @return true if validation succeeds + * @throws ArithmeticException if the query validation timeout does not fit as seconds in an int. + */ + @Override + public boolean validateObject(final UserPassKey ignored, final PooledObject pooledObject) { + return super.validateObject(pooledObject); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java index 89977bc9ac..f8308be5c3 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java @@ -1,1177 +1,1177 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.function.Supplier; - -import javax.naming.NamingException; -import javax.naming.Reference; -import javax.naming.StringRefAddr; -import javax.sql.ConnectionPoolDataSource; - -import org.apache.commons.dbcp2.SwallowedExceptionLogger; -import org.apache.commons.dbcp2.Utils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.commons.pool2.ObjectPool; -import org.apache.commons.pool2.impl.EvictionPolicy; -import org.apache.commons.pool2.impl.GenericObjectPool; - -/** - *

- * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration - * options, most of which are defined in the parent class. This datasource uses individual pools per user, and some - * properties can be set specifically for a given user, if the deployment environment can support initialization of - * mapped properties. So for example, a pool of admin or write-access Connections can be guaranteed a certain number of - * connections, separate from a maximum set for users with read-only connections. - *

- * - *

- * User passwords can be changed without re-initializing the datasource. When a - * {@code getConnection(userName, password)} request is processed with a password that is different from those used - * to create connections in the pool associated with {@code userName}, an attempt is made to create a new - * connection using the supplied password and if this succeeds, the existing pool is cleared and a new pool is created - * for connections using the new password. - *

- * - * @since 2.0 - */ -public class PerUserPoolDataSource extends InstanceKeyDataSource { - - private static final long serialVersionUID = 7872747993848065028L; - - private static final Log log = LogFactory.getLog(PerUserPoolDataSource.class); - - private static HashMap createMap() { - // Should there be a default size different from what this ctor provides? - return new HashMap<>(); - } - - /** - * Maps user names to a data source property: BlockWhenExhausted. - */ - private Map perUserBlockWhenExhausted; - - /** - * Maps user names to a data source property: EvictionPolicyClassName. - */ - private Map perUserEvictionPolicyClassName; - - /** - * Maps user names to a data source property: Lifo. - */ - private Map perUserLifo; - - /** - * Maps user names to a data source property: MaxIdle. - */ - private Map perUserMaxIdle; - - /** - * Maps user names to a data source property: MaxTotal. - */ - private Map perUserMaxTotal; - - /** - * Maps user names to a data source property: MaxWaitDuration. - */ - private Map perUserMaxWaitDuration; - - /** - * Maps user names to a data source property: MinEvictableIdleDuration. - */ - private Map perUserMinEvictableIdleDuration; - - /** - * Maps user names to a data source property: MinIdle. - */ - private Map perUserMinIdle; - - /** - * Maps user names to a data source property: NumTestsPerEvictionRun. - */ - private Map perUserNumTestsPerEvictionRun; - - /** - * Maps user names to a data source property: SoftMinEvictableIdleDuration. - */ - private Map perUserSoftMinEvictableIdleDuration; - - /** - * Maps user names to a data source property: TestOnCreate. - */ - private Map perUserTestOnCreate; - - /** - * Maps user names to a data source property: TestOnBorrow. - */ - private Map perUserTestOnBorrow; - - /** - * Maps user names to a data source property: TestOnReturn. - */ - private Map perUserTestOnReturn; - - /** - * Maps user names to a data source property: TestWhileIdle. - */ - private Map perUserTestWhileIdle; - - /** - * Maps user names to a data source property: DurationBetweenEvictionRuns. - */ - private Map perUserDurationBetweenEvictionRuns; - - /** - * Maps user names to a data source property: DefaultAutoCommit. - */ - private Map perUserDefaultAutoCommit; - - /** - * Maps user names to a data source property: DefaultTransactionIsolation. - */ - private Map perUserDefaultTransactionIsolation; - - /** - * Maps user names to a data source property: DefaultReadOnly. - */ - private Map perUserDefaultReadOnly; - - /** - * Map to keep track of Pools for a given user. - */ - private transient Map managers = createMap(); - - /** - * Constructs a new instance. - */ - public PerUserPoolDataSource() { - } - - /** - * Clears pool(s) maintained by this data source. - * - * @see org.apache.commons.pool2.ObjectPool#clear() - * @since 2.3.0 - */ - @SuppressWarnings("resource") // does not allocate a pool - public void clear() { - managers.values().forEach(manager -> { - try { - getCPDSConnectionFactoryPool(manager).clear(); - } catch (final Exception ignored) { - // ignore and try to close others. - } - }); - InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); - } - - /** - * Closes pool(s) maintained by this data source. - * - * @see org.apache.commons.pool2.ObjectPool#close() - */ - @Override - public void close() { - managers.values().forEach(manager -> Utils.closeQuietly(getCPDSConnectionFactoryPool(manager))); - InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); - } - - /** - * Converts a map with Long milliseconds values to another map with Duration values. - */ - private Map convertMap(final Map currentMap, final Map longMap) { - final Map durationMap = createMap(); - longMap.forEach((k, v) -> durationMap.put(k, toDurationOrNull(v))); - if (currentMap == null) { - return durationMap; - } - currentMap.clear(); - currentMap.putAll(durationMap); - return currentMap; - - } - - /** - * Gets the user specific default value in a map for the specified user's pool. - * - * @param userName The user name key. - * @return The user specific value. - */ - private V get(final Map map, final String userName) { - return map != null ? map.get(userName) : null; - } - - /** - * Gets the user specific default value in a map for the specified user's pool. - * - * @param userName The user name key. - * @return The user specific value. - */ - private V get(final Map map, final String userName, final Supplier defaultSupplier) { - final V v = get(map, userName); - return v != null ? v : defaultSupplier.get(); - } - - @Override - protected PooledConnectionManager getConnectionManager(final UserPassKey upKey) { - return managers.get(getPoolKey(upKey.getUserName())); - } - - /** - * Gets the underlying pre-allocated pool (does NOT allocate). - * - * @param manager A CPDSConnectionFactory. - * @return the underlying pool. - */ - private ObjectPool getCPDSConnectionFactoryPool(final PooledConnectionManager manager) { - return ((CPDSConnectionFactory) manager).getPool(); - } - - /** - * Gets the number of active connections in the default pool. - * - * @return The number of active connections in the default pool. - */ - public int getNumActive() { - return getNumActive(null); - } - - /** - * Gets the number of active connections in the pool for a given user. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - @SuppressWarnings("resource") - public int getNumActive(final String userName) { - final ObjectPool pool = getPool(getPoolKey(userName)); - return pool == null ? 0 : pool.getNumActive(); - } - - /** - * Gets the number of idle connections in the default pool. - * - * @return The number of idle connections in the default pool. - */ - public int getNumIdle() { - return getNumIdle(null); - } - - /** - * Gets the number of idle connections in the pool for a given user. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - @SuppressWarnings("resource") - public int getNumIdle(final String userName) { - final ObjectPool pool = getPool(getPoolKey(userName)); - return pool == null ? 0 : pool.getNumIdle(); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getBlockWhenExhausted()} for the specified user's pool - * or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserBlockWhenExhausted(final String userName) { - return get(perUserBlockWhenExhausted, userName, this::getDefaultBlockWhenExhausted); - } - - /** - * Gets the user specific default value for {@link Connection#setAutoCommit(boolean)} for the specified user's pool. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public Boolean getPerUserDefaultAutoCommit(final String userName) { - return get(perUserDefaultAutoCommit, userName); - } - - /** - * Gets the user specific default value for {@link Connection#setReadOnly(boolean)} for the specified user's pool. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public Boolean getPerUserDefaultReadOnly(final String userName) { - return get(perUserDefaultReadOnly, userName); - } - - /** - * Gets the user specific default value for {@link Connection#setTransactionIsolation(int)} for the specified user's - * pool. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public Integer getPerUserDefaultTransactionIsolation(final String userName) { - return get(perUserDefaultTransactionIsolation, userName); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @since 2.10.0 - */ - public Duration getPerUserDurationBetweenEvictionRuns(final String userName) { - return get(perUserDurationBetweenEvictionRuns, userName, this::getDefaultDurationBetweenEvictionRuns); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's - * pool or the default if no user specific value is defined. - *

- * The class must implement {@link EvictionPolicy}. - *

- * - * @param userName - * The user name key. - * @return The user specific value. - */ - public String getPerUserEvictionPolicyClassName(final String userName) { - return get(perUserEvictionPolicyClassName, userName, this::getDefaultEvictionPolicyClassName); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getLifo()} for the specified user's pool or the default - * if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserLifo(final String userName) { - return get(perUserLifo, userName, this::getDefaultLifo); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMaxIdle()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public int getPerUserMaxIdle(final String userName) { - return get(perUserMaxIdle, userName, this::getDefaultMaxIdle); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMaxTotal()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public int getPerUserMaxTotal(final String userName) { - return get(perUserMaxTotal, userName, this::getDefaultMaxTotal); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or - * the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @since 2.10.0 - */ - public Duration getPerUserMaxWaitDuration(final String userName) { - return get(perUserMaxWaitDuration, userName, this::getDefaultMaxWait); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or - * the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @deprecated Use {@link #getPerUserMaxWaitDuration}. - */ - @Deprecated - public long getPerUserMaxWaitMillis(final String userName) { - return getPerUserMaxWaitDuration(userName).toMillis(); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value, never null. - * @since 2.10.0 - */ - public Duration getPerUserMinEvictableIdleDuration(final String userName) { - return get(perUserMinEvictableIdleDuration, userName, this::getDefaultMinEvictableIdleDuration); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @deprecated Use {@link #getPerUserMinEvictableIdleDuration(String)}. - */ - @Deprecated - public long getPerUserMinEvictableIdleTimeMillis(final String userName) { - return getPerUserMinEvictableIdleDuration(userName).toMillis(); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getMinIdle()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public int getPerUserMinIdle(final String userName) { - return get(perUserMinIdle, userName, this::getDefaultMinIdle); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getNumTestsPerEvictionRun()} for the specified user's - * pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public int getPerUserNumTestsPerEvictionRun(final String userName) { - return get(perUserNumTestsPerEvictionRun, userName, this::getDefaultNumTestsPerEvictionRun); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @since 2.10.0 - */ - public Duration getPerUserSoftMinEvictableIdleDuration(final String userName) { - return get(perUserSoftMinEvictableIdleDuration, userName, this::getDefaultSoftMinEvictableIdleDuration); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @deprecated Use {@link #getPerUserSoftMinEvictableIdleDuration(String)}. - */ - @Deprecated - public long getPerUserSoftMinEvictableIdleTimeMillis(final String userName) { - return getPerUserSoftMinEvictableIdleDuration(userName).toMillis(); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getTestOnBorrow()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserTestOnBorrow(final String userName) { - return get(perUserTestOnBorrow, userName, this::getDefaultTestOnBorrow); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getTestOnCreate()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserTestOnCreate(final String userName) { - return get(perUserTestOnCreate, userName, this::getDefaultTestOnCreate); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getTestOnReturn()} for the specified user's pool or the - * default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserTestOnReturn(final String userName) { - return get(perUserTestOnReturn, userName, this::getDefaultTestOnReturn); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getTestWhileIdle()} for the specified user's pool or - * the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - */ - public boolean getPerUserTestWhileIdle(final String userName) { - return get(perUserTestWhileIdle, userName, this::getDefaultTestWhileIdle); - } - - /** - * Gets the user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified - * user's pool or the default if no user specific value is defined. - * - * @param userName - * The user name key. - * @return The user specific value. - * @deprecated Use {@link #getPerUserDurationBetweenEvictionRuns(String)}. - */ - @Deprecated - public long getPerUserTimeBetweenEvictionRunsMillis(final String userName) { - return getPerUserDurationBetweenEvictionRuns(userName).toMillis(); - } - - /** - * Returns the object pool associated with the given PoolKey. - * - * @param poolKey - * PoolKey identifying the pool - * @return the GenericObjectPool pooling connections for the userName and datasource specified by the PoolKey - */ - private ObjectPool getPool(final PoolKey poolKey) { - final CPDSConnectionFactory mgr = (CPDSConnectionFactory) managers.get(poolKey); - return mgr == null ? null : mgr.getPool(); - } - - @SuppressWarnings("resource") // does not allocate a pool - @Override - protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String password) throws SQLException { - final PoolKey key = getPoolKey(userName); - ObjectPool pool; - PooledConnectionManager manager; - synchronized (this) { - manager = managers.get(key); - if (manager == null) { - try { - registerPool(userName, password); - manager = managers.get(key); - } catch (final NamingException e) { - throw new SQLException("RegisterPool failed", e); - } - } - pool = getCPDSConnectionFactoryPool(manager); - } - PooledConnectionAndInfo info = null; - try { - info = pool.borrowObject(); - } catch (final NoSuchElementException ex) { - throw new SQLException("Could not retrieve connection info from pool", ex); - } catch (final Exception e) { - // See if failure is due to CPDSConnectionFactory authentication failure - try { - testCPDS(userName, password); - } catch (final Exception ex) { - throw new SQLException("Could not retrieve connection info from pool", ex); - } - // New password works, so kill the old pool, create a new one, and borrow - manager.closePool(userName); - synchronized (this) { - managers.remove(key); - } - try { - registerPool(userName, password); - pool = getPool(key); - } catch (final NamingException ne) { - throw new SQLException("RegisterPool failed", ne); - } - try { - info = pool.borrowObject(); - } catch (final Exception ex) { - throw new SQLException("Could not retrieve connection info from pool", ex); - } - } - return info; - } - - /** - * Creates a pool key from the provided parameters. - * - * @param userName - * User name - * @return The pool key - */ - private PoolKey getPoolKey(final String userName) { - return new PoolKey(getDataSourceName(), userName); - } - - /** - * Returns a {@code PerUserPoolDataSource} {@link Reference}. - */ - @Override - public Reference getReference() throws NamingException { - final Reference ref = new Reference(getClass().getName(), PerUserPoolDataSourceFactory.class.getName(), null); - ref.add(new StringRefAddr("instanceKey", getInstanceKey())); - return ref; - } - - Map put(Map map, final K key, final V value) { - if (map == null) { - map = createMap(); - } - map.put(key, value); - return map; - } - - /** - * Deserializes an instance from an ObjectInputStream. - * - * @param in The source ObjectInputStream. - * @throws IOException Any of the usual Input/Output related exceptions. - * @throws ClassNotFoundException A class of a serialized object cannot be found. - */ - @SuppressWarnings("resource") - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - this.managers = readObjectImpl().managers; - } - - private PerUserPoolDataSource readObjectImpl() throws IOException, ClassNotFoundException { - try { - return (PerUserPoolDataSource) new PerUserPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null); - } catch (final NamingException e) { - throw new IOException("NamingException: " + e); - } - } - - private synchronized void registerPool(final String userName, final String password) throws NamingException, SQLException { - final ConnectionPoolDataSource cpds = testCPDS(userName, password); - // Set up the factory we will use (passing the pool associates - // the factory with the pool, so we do not have to do so - // explicitly) - final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), - isRollbackAfterValidation(), userName, Utils.toCharArray(password)); - factory.setMaxConn(getMaxConnDuration()); - // Create an object pool to contain our PooledConnections - @SuppressWarnings("resource") - final GenericObjectPool pool = new GenericObjectPool<>(factory); - factory.setPool(pool); - pool.setBlockWhenExhausted(getPerUserBlockWhenExhausted(userName)); - pool.setEvictionPolicyClassName(getPerUserEvictionPolicyClassName(userName)); - pool.setLifo(getPerUserLifo(userName)); - pool.setMaxIdle(getPerUserMaxIdle(userName)); - pool.setMaxTotal(getPerUserMaxTotal(userName)); - pool.setMaxWait(getPerUserMaxWaitDuration(userName)); - pool.setMinEvictableIdleDuration(getPerUserMinEvictableIdleDuration(userName)); - pool.setMinIdle(getPerUserMinIdle(userName)); - pool.setNumTestsPerEvictionRun(getPerUserNumTestsPerEvictionRun(userName)); - pool.setSoftMinEvictableIdleDuration(getPerUserSoftMinEvictableIdleDuration(userName)); - pool.setTestOnCreate(getPerUserTestOnCreate(userName)); - pool.setTestOnBorrow(getPerUserTestOnBorrow(userName)); - pool.setTestOnReturn(getPerUserTestOnReturn(userName)); - pool.setTestWhileIdle(getPerUserTestWhileIdle(userName)); - pool.setDurationBetweenEvictionRuns(getPerUserDurationBetweenEvictionRuns(userName)); - pool.setSwallowedExceptionListener(new SwallowedExceptionLogger(log)); - final PoolKey poolKey = getPoolKey(userName); - if (managers.containsKey(poolKey)) { - pool.close(); - throw new IllegalStateException("Pool already contains an entry for this user/password: " + userName); - } - managers.put(poolKey, factory); - } - - private Map replaceAll(final Map currentMap, final Map newMap) { - if (currentMap == null) { - return new HashMap<>(newMap); - } - currentMap.clear(); - currentMap.putAll(newMap); - return currentMap; - } - - void setPerUserBlockWhenExhausted(final Map newMap) { - assertInitializationAllowed(); - perUserBlockWhenExhausted = replaceAll(perUserBlockWhenExhausted, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getBlockWhenExhausted()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserBlockWhenExhausted(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserBlockWhenExhausted = put(perUserBlockWhenExhausted, userName, value); - } - - void setPerUserDefaultAutoCommit(final Map newMap) { - assertInitializationAllowed(); - perUserDefaultAutoCommit = replaceAll(perUserDefaultAutoCommit, newMap); - } - - /** - * Sets a user specific default value for {@link Connection#setAutoCommit(boolean)} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserDefaultAutoCommit(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserDefaultAutoCommit = put(perUserDefaultAutoCommit, userName, value); - - } - - void setPerUserDefaultReadOnly(final Map newMap) { - assertInitializationAllowed(); - perUserDefaultReadOnly = replaceAll(perUserDefaultReadOnly, newMap); - } - - /** - * Sets a user specific default value for {@link Connection#setReadOnly(boolean)} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserDefaultReadOnly(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserDefaultReadOnly = put(perUserDefaultReadOnly, userName, value); - - } - - void setPerUserDefaultTransactionIsolation(final Map newMap) { - assertInitializationAllowed(); - perUserDefaultTransactionIsolation = replaceAll(perUserDefaultTransactionIsolation, newMap); - } - - /** - * Sets a user specific default value for {@link Connection#setTransactionIsolation(int)} for the specified user's - * pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserDefaultTransactionIsolation(final String userName, final Integer value) { - assertInitializationAllowed(); - perUserDefaultTransactionIsolation = put(perUserDefaultTransactionIsolation, userName, value); - - } - - void setPerUserDurationBetweenEvictionRuns(final Map newMap) { - assertInitializationAllowed(); - perUserDurationBetweenEvictionRuns = replaceAll(perUserDurationBetweenEvictionRuns, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified - * user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @since 2.10.0 - */ - public void setPerUserDurationBetweenEvictionRuns(final String userName, final Duration value) { - assertInitializationAllowed(); - perUserDurationBetweenEvictionRuns = put(perUserDurationBetweenEvictionRuns, userName, value); - - } - - void setPerUserEvictionPolicyClassName(final Map newMap) { - assertInitializationAllowed(); - perUserEvictionPolicyClassName = replaceAll(perUserEvictionPolicyClassName, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's - * pool. - *

- * The class must implement {@link EvictionPolicy}. - *

- * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserEvictionPolicyClassName(final String userName, final String value) { - assertInitializationAllowed(); - perUserEvictionPolicyClassName = put(perUserEvictionPolicyClassName, userName, value); - } - - void setPerUserLifo(final Map newMap) { - assertInitializationAllowed(); - perUserLifo = replaceAll(perUserLifo, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getLifo()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserLifo(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserLifo = put(perUserLifo, userName, value); - } - - void setPerUserMaxIdle(final Map newMap) { - assertInitializationAllowed(); - perUserMaxIdle = replaceAll(perUserMaxIdle, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMaxIdle()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserMaxIdle(final String userName, final Integer value) { - assertInitializationAllowed(); - perUserMaxIdle = put(perUserMaxIdle, userName, value); - } - - void setPerUserMaxTotal(final Map newMap) { - assertInitializationAllowed(); - perUserMaxTotal = replaceAll(perUserMaxTotal, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMaxTotal()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserMaxTotal(final String userName, final Integer value) { - assertInitializationAllowed(); - perUserMaxTotal = put(perUserMaxTotal, userName, value); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @since 2.10.0 - */ - public void setPerUserMaxWait(final String userName, final Duration value) { - assertInitializationAllowed(); - perUserMaxWaitDuration = put(perUserMaxWaitDuration, userName, value); - } - - void setPerUserMaxWaitDuration(final Map newMap) { - assertInitializationAllowed(); - perUserMaxWaitDuration = replaceAll(perUserMaxWaitDuration, newMap); - } - - void setPerUserMaxWaitMillis(final Map newMap) { - assertInitializationAllowed(); - perUserMaxWaitDuration = convertMap(perUserMaxWaitDuration, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @deprecated Use {@link #setPerUserMaxWait(String, Duration)}. - */ - @Deprecated - public void setPerUserMaxWaitMillis(final String userName, final Long value) { - setPerUserMaxWait(userName, toDurationOrNull(value)); - } - - void setPerUserMinEvictableIdle(final Map newMap) { - assertInitializationAllowed(); - perUserMinEvictableIdleDuration = replaceAll(perUserMinEvictableIdleDuration, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified user's - * pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @since 2.10.0 - */ - public void setPerUserMinEvictableIdle(final String userName, final Duration value) { - assertInitializationAllowed(); - perUserMinEvictableIdleDuration = put(perUserMinEvictableIdleDuration, userName, value); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified user's - * pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @deprecated Use {@link #setPerUserMinEvictableIdle(String, Duration)}. - */ - @Deprecated - public void setPerUserMinEvictableIdleTimeMillis(final String userName, final Long value) { - setPerUserMinEvictableIdle(userName, toDurationOrNull(value)); - } - - void setPerUserMinIdle(final Map newMap) { - assertInitializationAllowed(); - perUserMinIdle = replaceAll(perUserMinIdle, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getMinIdle()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserMinIdle(final String userName, final Integer value) { - assertInitializationAllowed(); - perUserMinIdle = put(perUserMinIdle, userName, value); - } - - void setPerUserNumTestsPerEvictionRun(final Map newMap) { - assertInitializationAllowed(); - perUserNumTestsPerEvictionRun = replaceAll(perUserNumTestsPerEvictionRun, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getNumTestsPerEvictionRun()} for the specified user's - * pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserNumTestsPerEvictionRun(final String userName, final Integer value) { - assertInitializationAllowed(); - perUserNumTestsPerEvictionRun = put(perUserNumTestsPerEvictionRun, userName, value); - } - - void setPerUserSoftMinEvictableIdle(final Map newMap) { - assertInitializationAllowed(); - perUserSoftMinEvictableIdleDuration = replaceAll(perUserSoftMinEvictableIdleDuration, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified - * user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @since 2.10.0 - */ - public void setPerUserSoftMinEvictableIdle(final String userName, final Duration value) { - assertInitializationAllowed(); - perUserSoftMinEvictableIdleDuration = put(perUserSoftMinEvictableIdleDuration, userName, value); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified - * user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @deprecated Use {@link #setPerUserSoftMinEvictableIdle(String, Duration)}. - */ - @Deprecated - public void setPerUserSoftMinEvictableIdleTimeMillis(final String userName, final Long value) { - setPerUserSoftMinEvictableIdle(userName, toDurationOrNull(value)); - } - - void setPerUserTestOnBorrow(final Map newMap) { - assertInitializationAllowed(); - perUserTestOnBorrow = replaceAll(perUserTestOnBorrow, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getTestOnBorrow()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserTestOnBorrow(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserTestOnBorrow = put(perUserTestOnBorrow, userName, value); - } - - void setPerUserTestOnCreate(final Map newMap) { - assertInitializationAllowed(); - perUserTestOnCreate = replaceAll(perUserTestOnCreate, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getTestOnCreate()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserTestOnCreate(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserTestOnCreate = put(perUserTestOnCreate, userName, value); - } - - void setPerUserTestOnReturn(final Map newMap) { - assertInitializationAllowed(); - perUserTestOnReturn = replaceAll(perUserTestOnReturn, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getTestOnReturn()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserTestOnReturn(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserTestOnReturn = put(perUserTestOnReturn, userName, value); - } - - void setPerUserTestWhileIdle(final Map newMap) { - assertInitializationAllowed(); - perUserTestWhileIdle = replaceAll(perUserTestWhileIdle, newMap); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getTestWhileIdle()} for the specified user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - */ - public void setPerUserTestWhileIdle(final String userName, final Boolean value) { - assertInitializationAllowed(); - perUserTestWhileIdle = put(perUserTestWhileIdle, userName, value); - } - - /** - * Sets a user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified - * user's pool. - * - * @param userName - * The user name key. - * @param value - * The user specific value. - * @deprecated Use {@link #setPerUserDurationBetweenEvictionRuns(String, Duration)}. - */ - @Deprecated - public void setPerUserTimeBetweenEvictionRunsMillis(final String userName, final Long value) { - setPerUserDurationBetweenEvictionRuns(userName, toDurationOrNull(value)); - } - - @Override - protected void setupDefaults(final Connection con, final String userName) throws SQLException { - Boolean defaultAutoCommit = isDefaultAutoCommit(); - if (userName != null) { - final Boolean userMax = getPerUserDefaultAutoCommit(userName); - if (userMax != null) { - defaultAutoCommit = userMax; - } - } - - Boolean defaultReadOnly = isDefaultReadOnly(); - if (userName != null) { - final Boolean userMax = getPerUserDefaultReadOnly(userName); - if (userMax != null) { - defaultReadOnly = userMax; - } - } - - int defaultTransactionIsolation = getDefaultTransactionIsolation(); - if (userName != null) { - final Integer userMax = getPerUserDefaultTransactionIsolation(userName); - if (userMax != null) { - defaultTransactionIsolation = userMax; - } - } - - if (defaultAutoCommit != null && con.getAutoCommit() != defaultAutoCommit) { - con.setAutoCommit(defaultAutoCommit); - } - - if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { - con.setTransactionIsolation(defaultTransactionIsolation); - } - - if (defaultReadOnly != null && con.isReadOnly() != defaultReadOnly) { - con.setReadOnly(defaultReadOnly); - } - } - - private Duration toDurationOrNull(final Long millis) { - return millis == null ? null : Duration.ofMillis(millis); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.sql.ConnectionPoolDataSource; + +import org.apache.commons.dbcp2.SwallowedExceptionLogger; +import org.apache.commons.dbcp2.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.EvictionPolicy; +import org.apache.commons.pool2.impl.GenericObjectPool; + +/** + *

+ * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration + * options, most of which are defined in the parent class. This datasource uses individual pools per user, and some + * properties can be set specifically for a given user, if the deployment environment can support initialization of + * mapped properties. So for example, a pool of admin or write-access Connections can be guaranteed a certain number of + * connections, separate from a maximum set for users with read-only connections. + *

+ * + *

+ * User passwords can be changed without re-initializing the datasource. When a + * {@code getConnection(userName, password)} request is processed with a password that is different from those used + * to create connections in the pool associated with {@code userName}, an attempt is made to create a new + * connection using the supplied password and if this succeeds, the existing pool is cleared and a new pool is created + * for connections using the new password. + *

+ * + * @since 2.0 + */ +public class PerUserPoolDataSource extends InstanceKeyDataSource { + + private static final long serialVersionUID = 7872747993848065028L; + + private static final Log log = LogFactory.getLog(PerUserPoolDataSource.class); + + private static HashMap createMap() { + // Should there be a default size different from what this ctor provides? + return new HashMap<>(); + } + + /** + * Maps user names to a data source property: BlockWhenExhausted. + */ + private Map perUserBlockWhenExhausted; + + /** + * Maps user names to a data source property: EvictionPolicyClassName. + */ + private Map perUserEvictionPolicyClassName; + + /** + * Maps user names to a data source property: Lifo. + */ + private Map perUserLifo; + + /** + * Maps user names to a data source property: MaxIdle. + */ + private Map perUserMaxIdle; + + /** + * Maps user names to a data source property: MaxTotal. + */ + private Map perUserMaxTotal; + + /** + * Maps user names to a data source property: MaxWaitDuration. + */ + private Map perUserMaxWaitDuration; + + /** + * Maps user names to a data source property: MinEvictableIdleDuration. + */ + private Map perUserMinEvictableIdleDuration; + + /** + * Maps user names to a data source property: MinIdle. + */ + private Map perUserMinIdle; + + /** + * Maps user names to a data source property: NumTestsPerEvictionRun. + */ + private Map perUserNumTestsPerEvictionRun; + + /** + * Maps user names to a data source property: SoftMinEvictableIdleDuration. + */ + private Map perUserSoftMinEvictableIdleDuration; + + /** + * Maps user names to a data source property: TestOnCreate. + */ + private Map perUserTestOnCreate; + + /** + * Maps user names to a data source property: TestOnBorrow. + */ + private Map perUserTestOnBorrow; + + /** + * Maps user names to a data source property: TestOnReturn. + */ + private Map perUserTestOnReturn; + + /** + * Maps user names to a data source property: TestWhileIdle. + */ + private Map perUserTestWhileIdle; + + /** + * Maps user names to a data source property: DurationBetweenEvictionRuns. + */ + private Map perUserDurationBetweenEvictionRuns; + + /** + * Maps user names to a data source property: DefaultAutoCommit. + */ + private Map perUserDefaultAutoCommit; + + /** + * Maps user names to a data source property: DefaultTransactionIsolation. + */ + private Map perUserDefaultTransactionIsolation; + + /** + * Maps user names to a data source property: DefaultReadOnly. + */ + private Map perUserDefaultReadOnly; + + /** + * Map to keep track of Pools for a given user. + */ + private transient Map managers = createMap(); + + /** + * Constructs a new instance. + */ + public PerUserPoolDataSource() { + } + + /** + * Clears pool(s) maintained by this data source. + * + * @see org.apache.commons.pool2.ObjectPool#clear() + * @since 2.3.0 + */ + @SuppressWarnings("resource") // does not allocate a pool + public void clear() { + managers.values().forEach(manager -> { + try { + getCPDSConnectionFactoryPool(manager).clear(); + } catch (final Exception ignored) { + // ignore and try to close others. + } + }); + InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); + } + + /** + * Closes pool(s) maintained by this data source. + * + * @see org.apache.commons.pool2.ObjectPool#close() + */ + @Override + public void close() { + managers.values().forEach(manager -> Utils.closeQuietly(getCPDSConnectionFactoryPool(manager))); + InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); + } + + /** + * Converts a map with Long milliseconds values to another map with Duration values. + */ + private Map convertMap(final Map currentMap, final Map longMap) { + final Map durationMap = createMap(); + longMap.forEach((k, v) -> durationMap.put(k, toDurationOrNull(v))); + if (currentMap == null) { + return durationMap; + } + currentMap.clear(); + currentMap.putAll(durationMap); + return currentMap; + + } + + /** + * Gets the user specific default value in a map for the specified user's pool. + * + * @param userName The user name key. + * @return The user specific value. + */ + private V get(final Map map, final String userName) { + return map != null ? map.get(userName) : null; + } + + /** + * Gets the user specific default value in a map for the specified user's pool. + * + * @param userName The user name key. + * @return The user specific value. + */ + private V get(final Map map, final String userName, final Supplier defaultSupplier) { + final V v = get(map, userName); + return v != null ? v : defaultSupplier.get(); + } + + @Override + protected PooledConnectionManager getConnectionManager(final UserPassKey upKey) { + return managers.get(getPoolKey(upKey.getUserName())); + } + + /** + * Gets the underlying pre-allocated pool (does NOT allocate). + * + * @param manager A CPDSConnectionFactory. + * @return the underlying pool. + */ + private ObjectPool getCPDSConnectionFactoryPool(final PooledConnectionManager manager) { + return ((CPDSConnectionFactory) manager).getPool(); + } + + /** + * Gets the number of active connections in the default pool. + * + * @return The number of active connections in the default pool. + */ + public int getNumActive() { + return getNumActive(null); + } + + /** + * Gets the number of active connections in the pool for a given user. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + @SuppressWarnings("resource") + public int getNumActive(final String userName) { + final ObjectPool pool = getPool(getPoolKey(userName)); + return pool == null ? 0 : pool.getNumActive(); + } + + /** + * Gets the number of idle connections in the default pool. + * + * @return The number of idle connections in the default pool. + */ + public int getNumIdle() { + return getNumIdle(null); + } + + /** + * Gets the number of idle connections in the pool for a given user. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + @SuppressWarnings("resource") + public int getNumIdle(final String userName) { + final ObjectPool pool = getPool(getPoolKey(userName)); + return pool == null ? 0 : pool.getNumIdle(); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getBlockWhenExhausted()} for the specified user's pool + * or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserBlockWhenExhausted(final String userName) { + return get(perUserBlockWhenExhausted, userName, this::getDefaultBlockWhenExhausted); + } + + /** + * Gets the user specific default value for {@link Connection#setAutoCommit(boolean)} for the specified user's pool. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public Boolean getPerUserDefaultAutoCommit(final String userName) { + return get(perUserDefaultAutoCommit, userName); + } + + /** + * Gets the user specific default value for {@link Connection#setReadOnly(boolean)} for the specified user's pool. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public Boolean getPerUserDefaultReadOnly(final String userName) { + return get(perUserDefaultReadOnly, userName); + } + + /** + * Gets the user specific default value for {@link Connection#setTransactionIsolation(int)} for the specified user's + * pool. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public Integer getPerUserDefaultTransactionIsolation(final String userName) { + return get(perUserDefaultTransactionIsolation, userName); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @since 2.10.0 + */ + public Duration getPerUserDurationBetweenEvictionRuns(final String userName) { + return get(perUserDurationBetweenEvictionRuns, userName, this::getDefaultDurationBetweenEvictionRuns); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's + * pool or the default if no user specific value is defined. + *

+ * The class must implement {@link EvictionPolicy}. + *

+ * + * @param userName + * The user name key. + * @return The user specific value. + */ + public String getPerUserEvictionPolicyClassName(final String userName) { + return get(perUserEvictionPolicyClassName, userName, this::getDefaultEvictionPolicyClassName); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getLifo()} for the specified user's pool or the default + * if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserLifo(final String userName) { + return get(perUserLifo, userName, this::getDefaultLifo); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMaxIdle()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public int getPerUserMaxIdle(final String userName) { + return get(perUserMaxIdle, userName, this::getDefaultMaxIdle); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMaxTotal()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public int getPerUserMaxTotal(final String userName) { + return get(perUserMaxTotal, userName, this::getDefaultMaxTotal); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or + * the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @since 2.10.0 + */ + public Duration getPerUserMaxWaitDuration(final String userName) { + return get(perUserMaxWaitDuration, userName, this::getDefaultMaxWait); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or + * the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @deprecated Use {@link #getPerUserMaxWaitDuration}. + */ + @Deprecated + public long getPerUserMaxWaitMillis(final String userName) { + return getPerUserMaxWaitDuration(userName).toMillis(); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value, never null. + * @since 2.10.0 + */ + public Duration getPerUserMinEvictableIdleDuration(final String userName) { + return get(perUserMinEvictableIdleDuration, userName, this::getDefaultMinEvictableIdleDuration); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @deprecated Use {@link #getPerUserMinEvictableIdleDuration(String)}. + */ + @Deprecated + public long getPerUserMinEvictableIdleTimeMillis(final String userName) { + return getPerUserMinEvictableIdleDuration(userName).toMillis(); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getMinIdle()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public int getPerUserMinIdle(final String userName) { + return get(perUserMinIdle, userName, this::getDefaultMinIdle); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getNumTestsPerEvictionRun()} for the specified user's + * pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public int getPerUserNumTestsPerEvictionRun(final String userName) { + return get(perUserNumTestsPerEvictionRun, userName, this::getDefaultNumTestsPerEvictionRun); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @since 2.10.0 + */ + public Duration getPerUserSoftMinEvictableIdleDuration(final String userName) { + return get(perUserSoftMinEvictableIdleDuration, userName, this::getDefaultSoftMinEvictableIdleDuration); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @deprecated Use {@link #getPerUserSoftMinEvictableIdleDuration(String)}. + */ + @Deprecated + public long getPerUserSoftMinEvictableIdleTimeMillis(final String userName) { + return getPerUserSoftMinEvictableIdleDuration(userName).toMillis(); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getTestOnBorrow()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserTestOnBorrow(final String userName) { + return get(perUserTestOnBorrow, userName, this::getDefaultTestOnBorrow); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getTestOnCreate()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserTestOnCreate(final String userName) { + return get(perUserTestOnCreate, userName, this::getDefaultTestOnCreate); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getTestOnReturn()} for the specified user's pool or the + * default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserTestOnReturn(final String userName) { + return get(perUserTestOnReturn, userName, this::getDefaultTestOnReturn); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getTestWhileIdle()} for the specified user's pool or + * the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + */ + public boolean getPerUserTestWhileIdle(final String userName) { + return get(perUserTestWhileIdle, userName, this::getDefaultTestWhileIdle); + } + + /** + * Gets the user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified + * user's pool or the default if no user specific value is defined. + * + * @param userName + * The user name key. + * @return The user specific value. + * @deprecated Use {@link #getPerUserDurationBetweenEvictionRuns(String)}. + */ + @Deprecated + public long getPerUserTimeBetweenEvictionRunsMillis(final String userName) { + return getPerUserDurationBetweenEvictionRuns(userName).toMillis(); + } + + /** + * Returns the object pool associated with the given PoolKey. + * + * @param poolKey + * PoolKey identifying the pool + * @return the GenericObjectPool pooling connections for the userName and datasource specified by the PoolKey + */ + private ObjectPool getPool(final PoolKey poolKey) { + final CPDSConnectionFactory mgr = (CPDSConnectionFactory) managers.get(poolKey); + return mgr == null ? null : mgr.getPool(); + } + + @SuppressWarnings("resource") // does not allocate a pool + @Override + protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String password) throws SQLException { + final PoolKey key = getPoolKey(userName); + ObjectPool pool; + PooledConnectionManager manager; + synchronized (this) { + manager = managers.get(key); + if (manager == null) { + try { + registerPool(userName, password); + manager = managers.get(key); + } catch (final NamingException e) { + throw new SQLException("RegisterPool failed", e); + } + } + pool = getCPDSConnectionFactoryPool(manager); + } + PooledConnectionAndInfo info = null; + try { + info = pool.borrowObject(); + } catch (final NoSuchElementException ex) { + throw new SQLException("Could not retrieve connection info from pool", ex); + } catch (final Exception e) { + // See if failure is due to CPDSConnectionFactory authentication failure + try { + testCPDS(userName, password); + } catch (final Exception ex) { + throw new SQLException("Could not retrieve connection info from pool", ex); + } + // New password works, so kill the old pool, create a new one, and borrow + manager.closePool(userName); + synchronized (this) { + managers.remove(key); + } + try { + registerPool(userName, password); + pool = getPool(key); + } catch (final NamingException ne) { + throw new SQLException("RegisterPool failed", ne); + } + try { + info = pool.borrowObject(); + } catch (final Exception ex) { + throw new SQLException("Could not retrieve connection info from pool", ex); + } + } + return info; + } + + /** + * Creates a pool key from the provided parameters. + * + * @param userName + * User name + * @return The pool key + */ + private PoolKey getPoolKey(final String userName) { + return new PoolKey(getDataSourceName(), userName); + } + + /** + * Returns a {@code PerUserPoolDataSource} {@link Reference}. + */ + @Override + public Reference getReference() throws NamingException { + final Reference ref = new Reference(getClass().getName(), PerUserPoolDataSourceFactory.class.getName(), null); + ref.add(new StringRefAddr("instanceKey", getInstanceKey())); + return ref; + } + + Map put(Map map, final K key, final V value) { + if (map == null) { + map = createMap(); + } + map.put(key, value); + return map; + } + + /** + * Deserializes an instance from an ObjectInputStream. + * + * @param in The source ObjectInputStream. + * @throws IOException Any of the usual Input/Output related exceptions. + * @throws ClassNotFoundException A class of a serialized object cannot be found. + */ + @SuppressWarnings("resource") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.managers = readObjectImpl().managers; + } + + private PerUserPoolDataSource readObjectImpl() throws IOException, ClassNotFoundException { + try { + return (PerUserPoolDataSource) new PerUserPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null); + } catch (final NamingException e) { + throw new IOException("NamingException: " + e); + } + } + + private synchronized void registerPool(final String userName, final String password) throws NamingException, SQLException { + final ConnectionPoolDataSource cpds = testCPDS(userName, password); + // Set up the factory we will use (passing the pool associates + // the factory with the pool, so we do not have to do so + // explicitly) + final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), + isRollbackAfterValidation(), userName, Utils.toCharArray(password)); + factory.setMaxConn(getMaxConnDuration()); + // Create an object pool to contain our PooledConnections + @SuppressWarnings("resource") + final GenericObjectPool pool = new GenericObjectPool<>(factory); + factory.setPool(pool); + pool.setBlockWhenExhausted(getPerUserBlockWhenExhausted(userName)); + pool.setEvictionPolicyClassName(getPerUserEvictionPolicyClassName(userName)); + pool.setLifo(getPerUserLifo(userName)); + pool.setMaxIdle(getPerUserMaxIdle(userName)); + pool.setMaxTotal(getPerUserMaxTotal(userName)); + pool.setMaxWait(getPerUserMaxWaitDuration(userName)); + pool.setMinEvictableIdleDuration(getPerUserMinEvictableIdleDuration(userName)); + pool.setMinIdle(getPerUserMinIdle(userName)); + pool.setNumTestsPerEvictionRun(getPerUserNumTestsPerEvictionRun(userName)); + pool.setSoftMinEvictableIdleDuration(getPerUserSoftMinEvictableIdleDuration(userName)); + pool.setTestOnCreate(getPerUserTestOnCreate(userName)); + pool.setTestOnBorrow(getPerUserTestOnBorrow(userName)); + pool.setTestOnReturn(getPerUserTestOnReturn(userName)); + pool.setTestWhileIdle(getPerUserTestWhileIdle(userName)); + pool.setDurationBetweenEvictionRuns(getPerUserDurationBetweenEvictionRuns(userName)); + pool.setSwallowedExceptionListener(new SwallowedExceptionLogger(log)); + final PoolKey poolKey = getPoolKey(userName); + if (managers.containsKey(poolKey)) { + pool.close(); + throw new IllegalStateException("Pool already contains an entry for this user/password: " + userName); + } + managers.put(poolKey, factory); + } + + private Map replaceAll(final Map currentMap, final Map newMap) { + if (currentMap == null) { + return new HashMap<>(newMap); + } + currentMap.clear(); + currentMap.putAll(newMap); + return currentMap; + } + + void setPerUserBlockWhenExhausted(final Map newMap) { + assertInitializationAllowed(); + perUserBlockWhenExhausted = replaceAll(perUserBlockWhenExhausted, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getBlockWhenExhausted()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserBlockWhenExhausted(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserBlockWhenExhausted = put(perUserBlockWhenExhausted, userName, value); + } + + void setPerUserDefaultAutoCommit(final Map newMap) { + assertInitializationAllowed(); + perUserDefaultAutoCommit = replaceAll(perUserDefaultAutoCommit, newMap); + } + + /** + * Sets a user specific default value for {@link Connection#setAutoCommit(boolean)} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserDefaultAutoCommit(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserDefaultAutoCommit = put(perUserDefaultAutoCommit, userName, value); + + } + + void setPerUserDefaultReadOnly(final Map newMap) { + assertInitializationAllowed(); + perUserDefaultReadOnly = replaceAll(perUserDefaultReadOnly, newMap); + } + + /** + * Sets a user specific default value for {@link Connection#setReadOnly(boolean)} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserDefaultReadOnly(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserDefaultReadOnly = put(perUserDefaultReadOnly, userName, value); + + } + + void setPerUserDefaultTransactionIsolation(final Map newMap) { + assertInitializationAllowed(); + perUserDefaultTransactionIsolation = replaceAll(perUserDefaultTransactionIsolation, newMap); + } + + /** + * Sets a user specific default value for {@link Connection#setTransactionIsolation(int)} for the specified user's + * pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserDefaultTransactionIsolation(final String userName, final Integer value) { + assertInitializationAllowed(); + perUserDefaultTransactionIsolation = put(perUserDefaultTransactionIsolation, userName, value); + + } + + void setPerUserDurationBetweenEvictionRuns(final Map newMap) { + assertInitializationAllowed(); + perUserDurationBetweenEvictionRuns = replaceAll(perUserDurationBetweenEvictionRuns, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified + * user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @since 2.10.0 + */ + public void setPerUserDurationBetweenEvictionRuns(final String userName, final Duration value) { + assertInitializationAllowed(); + perUserDurationBetweenEvictionRuns = put(perUserDurationBetweenEvictionRuns, userName, value); + + } + + void setPerUserEvictionPolicyClassName(final Map newMap) { + assertInitializationAllowed(); + perUserEvictionPolicyClassName = replaceAll(perUserEvictionPolicyClassName, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's + * pool. + *

+ * The class must implement {@link EvictionPolicy}. + *

+ * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserEvictionPolicyClassName(final String userName, final String value) { + assertInitializationAllowed(); + perUserEvictionPolicyClassName = put(perUserEvictionPolicyClassName, userName, value); + } + + void setPerUserLifo(final Map newMap) { + assertInitializationAllowed(); + perUserLifo = replaceAll(perUserLifo, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getLifo()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserLifo(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserLifo = put(perUserLifo, userName, value); + } + + void setPerUserMaxIdle(final Map newMap) { + assertInitializationAllowed(); + perUserMaxIdle = replaceAll(perUserMaxIdle, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMaxIdle()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserMaxIdle(final String userName, final Integer value) { + assertInitializationAllowed(); + perUserMaxIdle = put(perUserMaxIdle, userName, value); + } + + void setPerUserMaxTotal(final Map newMap) { + assertInitializationAllowed(); + perUserMaxTotal = replaceAll(perUserMaxTotal, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMaxTotal()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserMaxTotal(final String userName, final Integer value) { + assertInitializationAllowed(); + perUserMaxTotal = put(perUserMaxTotal, userName, value); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @since 2.10.0 + */ + public void setPerUserMaxWait(final String userName, final Duration value) { + assertInitializationAllowed(); + perUserMaxWaitDuration = put(perUserMaxWaitDuration, userName, value); + } + + void setPerUserMaxWaitDuration(final Map newMap) { + assertInitializationAllowed(); + perUserMaxWaitDuration = replaceAll(perUserMaxWaitDuration, newMap); + } + + void setPerUserMaxWaitMillis(final Map newMap) { + assertInitializationAllowed(); + perUserMaxWaitDuration = convertMap(perUserMaxWaitDuration, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @deprecated Use {@link #setPerUserMaxWait(String, Duration)}. + */ + @Deprecated + public void setPerUserMaxWaitMillis(final String userName, final Long value) { + setPerUserMaxWait(userName, toDurationOrNull(value)); + } + + void setPerUserMinEvictableIdle(final Map newMap) { + assertInitializationAllowed(); + perUserMinEvictableIdleDuration = replaceAll(perUserMinEvictableIdleDuration, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified user's + * pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @since 2.10.0 + */ + public void setPerUserMinEvictableIdle(final String userName, final Duration value) { + assertInitializationAllowed(); + perUserMinEvictableIdleDuration = put(perUserMinEvictableIdleDuration, userName, value); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified user's + * pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @deprecated Use {@link #setPerUserMinEvictableIdle(String, Duration)}. + */ + @Deprecated + public void setPerUserMinEvictableIdleTimeMillis(final String userName, final Long value) { + setPerUserMinEvictableIdle(userName, toDurationOrNull(value)); + } + + void setPerUserMinIdle(final Map newMap) { + assertInitializationAllowed(); + perUserMinIdle = replaceAll(perUserMinIdle, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getMinIdle()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserMinIdle(final String userName, final Integer value) { + assertInitializationAllowed(); + perUserMinIdle = put(perUserMinIdle, userName, value); + } + + void setPerUserNumTestsPerEvictionRun(final Map newMap) { + assertInitializationAllowed(); + perUserNumTestsPerEvictionRun = replaceAll(perUserNumTestsPerEvictionRun, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getNumTestsPerEvictionRun()} for the specified user's + * pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserNumTestsPerEvictionRun(final String userName, final Integer value) { + assertInitializationAllowed(); + perUserNumTestsPerEvictionRun = put(perUserNumTestsPerEvictionRun, userName, value); + } + + void setPerUserSoftMinEvictableIdle(final Map newMap) { + assertInitializationAllowed(); + perUserSoftMinEvictableIdleDuration = replaceAll(perUserSoftMinEvictableIdleDuration, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified + * user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @since 2.10.0 + */ + public void setPerUserSoftMinEvictableIdle(final String userName, final Duration value) { + assertInitializationAllowed(); + perUserSoftMinEvictableIdleDuration = put(perUserSoftMinEvictableIdleDuration, userName, value); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified + * user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @deprecated Use {@link #setPerUserSoftMinEvictableIdle(String, Duration)}. + */ + @Deprecated + public void setPerUserSoftMinEvictableIdleTimeMillis(final String userName, final Long value) { + setPerUserSoftMinEvictableIdle(userName, toDurationOrNull(value)); + } + + void setPerUserTestOnBorrow(final Map newMap) { + assertInitializationAllowed(); + perUserTestOnBorrow = replaceAll(perUserTestOnBorrow, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getTestOnBorrow()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserTestOnBorrow(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserTestOnBorrow = put(perUserTestOnBorrow, userName, value); + } + + void setPerUserTestOnCreate(final Map newMap) { + assertInitializationAllowed(); + perUserTestOnCreate = replaceAll(perUserTestOnCreate, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getTestOnCreate()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserTestOnCreate(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserTestOnCreate = put(perUserTestOnCreate, userName, value); + } + + void setPerUserTestOnReturn(final Map newMap) { + assertInitializationAllowed(); + perUserTestOnReturn = replaceAll(perUserTestOnReturn, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getTestOnReturn()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserTestOnReturn(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserTestOnReturn = put(perUserTestOnReturn, userName, value); + } + + void setPerUserTestWhileIdle(final Map newMap) { + assertInitializationAllowed(); + perUserTestWhileIdle = replaceAll(perUserTestWhileIdle, newMap); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getTestWhileIdle()} for the specified user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + */ + public void setPerUserTestWhileIdle(final String userName, final Boolean value) { + assertInitializationAllowed(); + perUserTestWhileIdle = put(perUserTestWhileIdle, userName, value); + } + + /** + * Sets a user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified + * user's pool. + * + * @param userName + * The user name key. + * @param value + * The user specific value. + * @deprecated Use {@link #setPerUserDurationBetweenEvictionRuns(String, Duration)}. + */ + @Deprecated + public void setPerUserTimeBetweenEvictionRunsMillis(final String userName, final Long value) { + setPerUserDurationBetweenEvictionRuns(userName, toDurationOrNull(value)); + } + + @Override + protected void setupDefaults(final Connection con, final String userName) throws SQLException { + Boolean defaultAutoCommit = isDefaultAutoCommit(); + if (userName != null) { + final Boolean userMax = getPerUserDefaultAutoCommit(userName); + if (userMax != null) { + defaultAutoCommit = userMax; + } + } + + Boolean defaultReadOnly = isDefaultReadOnly(); + if (userName != null) { + final Boolean userMax = getPerUserDefaultReadOnly(userName); + if (userMax != null) { + defaultReadOnly = userMax; + } + } + + int defaultTransactionIsolation = getDefaultTransactionIsolation(); + if (userName != null) { + final Integer userMax = getPerUserDefaultTransactionIsolation(userName); + if (userMax != null) { + defaultTransactionIsolation = userMax; + } + } + + if (defaultAutoCommit != null && con.getAutoCommit() != defaultAutoCommit) { + con.setAutoCommit(defaultAutoCommit); + } + + if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { + con.setTransactionIsolation(defaultTransactionIsolation); + } + + if (defaultReadOnly != null && con.isReadOnly() != defaultReadOnly) { + con.setReadOnly(defaultReadOnly); + } + } + + private Duration toDurationOrNull(final Long millis) { + return millis == null ? null : Duration.ofMillis(millis); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/PoolKey.java b/src/main/java/org/apache/commons/dbcp2/datasources/PoolKey.java index 31be5411fb..cba9dbb31c 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/PoolKey.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/PoolKey.java @@ -1,69 +1,69 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.Serializable; -import java.util.Objects; - -/** - * The key type for entries in a {@link PerUserPoolDataSource}. - * - * @since 2.0 - */ -final class PoolKey implements Serializable { - private static final long serialVersionUID = 2252771047542484533L; - - private final String dataSourceName; - private final String userName; - - PoolKey(final String dataSourceName, final String userName) { - this.dataSourceName = dataSourceName; - this.userName = userName; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final PoolKey other = (PoolKey) obj; - if (!Objects.equals(dataSourceName, other.dataSourceName)) { - return false; - } - return Objects.equals(userName, other.userName); - } - - @Override - public int hashCode() { - return Objects.hash(dataSourceName, userName); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(50); - sb.append("PoolKey("); - sb.append(dataSourceName); - sb.append(')'); - return sb.toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.Serializable; +import java.util.Objects; + +/** + * The key type for entries in a {@link PerUserPoolDataSource}. + * + * @since 2.0 + */ +final class PoolKey implements Serializable { + private static final long serialVersionUID = 2252771047542484533L; + + private final String dataSourceName; + private final String userName; + + PoolKey(final String dataSourceName, final String userName) { + this.dataSourceName = dataSourceName; + this.userName = userName; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PoolKey other = (PoolKey) obj; + if (!Objects.equals(dataSourceName, other.dataSourceName)) { + return false; + } + return Objects.equals(userName, other.userName); + } + + @Override + public int hashCode() { + return Objects.hash(dataSourceName, userName); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(50); + sb.append("PoolKey("); + sb.append(dataSourceName); + sb.append(')'); + return sb.toString(); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/PooledConnectionManager.java b/src/main/java/org/apache/commons/dbcp2/datasources/PooledConnectionManager.java index 91efeb11df..fcdcb750eb 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/PooledConnectionManager.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/PooledConnectionManager.java @@ -1,68 +1,68 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.sql.SQLException; - -import javax.sql.PooledConnection; - -/** - * Methods to manage PoolableConnections and the connection pools that source them. - * - * @since 2.0 - */ -interface PooledConnectionManager { - - /** - * Closes the connection pool associated with the given user. - * - * @param userName - * user name - * @throws SQLException - * if an error occurs closing idle connections in the pool - */ - void closePool(String userName) throws SQLException; - - /** - * Closes the PooledConnection and remove it from the connection pool to which it belongs, adjusting pool counters. - * - * @param pc - * PooledConnection to be invalidated - * @throws SQLException - * if an SQL error occurs closing the connection - */ - void invalidate(PooledConnection pc) throws SQLException; - -// /** -// * Sets the database password used when creating connections. -// * -// * @param password password used when authenticating to the database -// * @since 2.10.0 -// */ -// default void setPassword(char[] password) { -// setPassword(String.copyValueOf(password)); -// } - - /** - * Sets the database password used when creating connections. - * - * @param password - * password used when authenticating to the database - */ - void setPassword(String password); - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.sql.SQLException; + +import javax.sql.PooledConnection; + +/** + * Methods to manage PoolableConnections and the connection pools that source them. + * + * @since 2.0 + */ +interface PooledConnectionManager { + + /** + * Closes the connection pool associated with the given user. + * + * @param userName + * user name + * @throws SQLException + * if an error occurs closing idle connections in the pool + */ + void closePool(String userName) throws SQLException; + + /** + * Closes the PooledConnection and remove it from the connection pool to which it belongs, adjusting pool counters. + * + * @param pc + * PooledConnection to be invalidated + * @throws SQLException + * if an SQL error occurs closing the connection + */ + void invalidate(PooledConnection pc) throws SQLException; + +// /** +// * Sets the database password used when creating connections. +// * +// * @param password password used when authenticating to the database +// * @since 2.10.0 +// */ +// default void setPassword(char[] password) { +// setPassword(String.copyValueOf(password)); +// } + + /** + * Sets the database password used when creating connections. + * + * @param password + * password used when authenticating to the database + */ + void setPassword(String password); + +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java index cdfcab7798..d6697b22e9 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java @@ -1,238 +1,238 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.sql.Connection; -import java.sql.SQLException; - -import javax.naming.NamingException; -import javax.naming.Reference; -import javax.naming.StringRefAddr; -import javax.sql.ConnectionPoolDataSource; - -import org.apache.commons.dbcp2.PoolableConnection; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.KeyedPooledObjectFactory; -import org.apache.commons.pool2.impl.GenericKeyedObjectPool; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; - -/** - *

- * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration - * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number - * of Connections in this data source. - *

- * - *

- * User passwords can be changed without re-initializing the data source. When a - * {@code getConnection(user name, password)} request is processed with a password that is different from those - * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new - * connection using the supplied password and if this succeeds, idle connections created using the old password are - * destroyed and new connections are created using the new password. - *

- * - * @since 2.0 - */ -public class SharedPoolDataSource extends InstanceKeyDataSource { - - private static final long serialVersionUID = -1458539734480586454L; - - /** - * Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}. - */ - private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; - - /** - * Maps user credentials to pooled connection with credentials. - */ - private transient KeyedObjectPool pool; - - /** - * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s. - */ - private transient KeyedCPDSConnectionFactory factory; - - /** - * Default no-argument constructor for Serialization - */ - public SharedPoolDataSource() { - // empty. - } - - /** - * Closes pool being maintained by this data source. - */ - @Override - public void close() throws SQLException { - if (pool != null) { - pool.close(); - } - InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); - } - - @Override - protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) { - return factory; - } - - /** - * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. - * - * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. - */ - public int getMaxTotal() { - return this.maxTotal; - } - - /** - * Gets the number of active connections in the pool. - * - * @return The number of active connections in the pool. - */ - public int getNumActive() { - return pool == null ? 0 : pool.getNumActive(); - } - - /** - * Gets the number of idle connections in the pool. - * - * @return The number of idle connections in the pool. - */ - public int getNumIdle() { - return pool == null ? 0 : pool.getNumIdle(); - } - - @Override - protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword) - throws SQLException { - - synchronized (this) { - if (pool == null) { - try { - registerPool(userName, userPassword); - } catch (final NamingException e) { - throw new SQLException("registerPool failed", e); - } - } - } - - try { - return pool.borrowObject(new UserPassKey(userName, userPassword)); - } catch (final Exception e) { - throw new SQLException("Could not retrieve connection info from pool", e); - } - } - - /** - * Creates a new {@link Reference} to a {@link SharedPoolDataSource}. - */ - @Override - public Reference getReference() throws NamingException { - final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null); - ref.add(new StringRefAddr("instanceKey", getInstanceKey())); - return ref; - } - - /** - * Deserializes an instance from an ObjectInputStream. - * - * @param in The source ObjectInputStream. - * @throws IOException Any of the usual Input/Output related exceptions. - * @throws ClassNotFoundException A class of a serialized object cannot be found. - */ - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - this.pool = readObjectImpl(); - } - - private KeyedObjectPool readObjectImpl() throws IOException, ClassNotFoundException { - try { - return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool; - } catch (final NamingException e) { - throw new IOException("NamingException: " + e); - } - } - - private void registerPool(final String userName, final String password) throws NamingException, SQLException { - - final ConnectionPoolDataSource cpds = testCPDS(userName, password); - - // Create an object pool to contain our PooledConnections - factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation()); - factory.setMaxConn(getMaxConnDuration()); - - final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); - config.setBlockWhenExhausted(getDefaultBlockWhenExhausted()); - config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName()); - config.setLifo(getDefaultLifo()); - config.setMaxIdlePerKey(getDefaultMaxIdle()); - config.setMaxTotal(getMaxTotal()); - config.setMaxTotalPerKey(getDefaultMaxTotal()); - config.setMaxWait(getDefaultMaxWait()); - config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration()); - config.setMinIdlePerKey(getDefaultMinIdle()); - config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun()); - config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration()); - config.setTestOnCreate(getDefaultTestOnCreate()); - config.setTestOnBorrow(getDefaultTestOnBorrow()); - config.setTestOnReturn(getDefaultTestOnReturn()); - config.setTestWhileIdle(getDefaultTestWhileIdle()); - config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns()); - - final KeyedObjectPool tmpPool = new GenericKeyedObjectPool<>(factory, config); - factory.setPool(tmpPool); - pool = tmpPool; - } - - /** - * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. - * - * @param maxTotal - * {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. - */ - public void setMaxTotal(final int maxTotal) { - assertInitializationAllowed(); - this.maxTotal = maxTotal; - } - - @Override - protected void setupDefaults(final Connection connection, final String userName) throws SQLException { - final Boolean defaultAutoCommit = isDefaultAutoCommit(); - if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) { - connection.setAutoCommit(defaultAutoCommit); - } - - final int defaultTransactionIsolation = getDefaultTransactionIsolation(); - if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { - connection.setTransactionIsolation(defaultTransactionIsolation); - } - - final Boolean defaultReadOnly = isDefaultReadOnly(); - if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) { - connection.setReadOnly(defaultReadOnly); - } - } - - @Override - protected void toStringFields(final StringBuilder builder) { - super.toStringFields(builder); - builder.append(", maxTotal="); - builder.append(maxTotal); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.sql.Connection; +import java.sql.SQLException; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.sql.ConnectionPoolDataSource; + +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + *

+ * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration + * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number + * of Connections in this data source. + *

+ * + *

+ * User passwords can be changed without re-initializing the data source. When a + * {@code getConnection(user name, password)} request is processed with a password that is different from those + * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new + * connection using the supplied password and if this succeeds, idle connections created using the old password are + * destroyed and new connections are created using the new password. + *

+ * + * @since 2.0 + */ +public class SharedPoolDataSource extends InstanceKeyDataSource { + + private static final long serialVersionUID = -1458539734480586454L; + + /** + * Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}. + */ + private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; + + /** + * Maps user credentials to pooled connection with credentials. + */ + private transient KeyedObjectPool pool; + + /** + * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s. + */ + private transient KeyedCPDSConnectionFactory factory; + + /** + * Default no-argument constructor for Serialization + */ + public SharedPoolDataSource() { + // empty. + } + + /** + * Closes pool being maintained by this data source. + */ + @Override + public void close() throws SQLException { + if (pool != null) { + pool.close(); + } + InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); + } + + @Override + protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) { + return factory; + } + + /** + * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. + * + * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. + */ + public int getMaxTotal() { + return this.maxTotal; + } + + /** + * Gets the number of active connections in the pool. + * + * @return The number of active connections in the pool. + */ + public int getNumActive() { + return pool == null ? 0 : pool.getNumActive(); + } + + /** + * Gets the number of idle connections in the pool. + * + * @return The number of idle connections in the pool. + */ + public int getNumIdle() { + return pool == null ? 0 : pool.getNumIdle(); + } + + @Override + protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword) + throws SQLException { + + synchronized (this) { + if (pool == null) { + try { + registerPool(userName, userPassword); + } catch (final NamingException e) { + throw new SQLException("registerPool failed", e); + } + } + } + + try { + return pool.borrowObject(new UserPassKey(userName, userPassword)); + } catch (final Exception e) { + throw new SQLException("Could not retrieve connection info from pool", e); + } + } + + /** + * Creates a new {@link Reference} to a {@link SharedPoolDataSource}. + */ + @Override + public Reference getReference() throws NamingException { + final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null); + ref.add(new StringRefAddr("instanceKey", getInstanceKey())); + return ref; + } + + /** + * Deserializes an instance from an ObjectInputStream. + * + * @param in The source ObjectInputStream. + * @throws IOException Any of the usual Input/Output related exceptions. + * @throws ClassNotFoundException A class of a serialized object cannot be found. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.pool = readObjectImpl(); + } + + private KeyedObjectPool readObjectImpl() throws IOException, ClassNotFoundException { + try { + return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool; + } catch (final NamingException e) { + throw new IOException("NamingException: " + e); + } + } + + private void registerPool(final String userName, final String password) throws NamingException, SQLException { + + final ConnectionPoolDataSource cpds = testCPDS(userName, password); + + // Create an object pool to contain our PooledConnections + factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation()); + factory.setMaxConn(getMaxConnDuration()); + + final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); + config.setBlockWhenExhausted(getDefaultBlockWhenExhausted()); + config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName()); + config.setLifo(getDefaultLifo()); + config.setMaxIdlePerKey(getDefaultMaxIdle()); + config.setMaxTotal(getMaxTotal()); + config.setMaxTotalPerKey(getDefaultMaxTotal()); + config.setMaxWait(getDefaultMaxWait()); + config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration()); + config.setMinIdlePerKey(getDefaultMinIdle()); + config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun()); + config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration()); + config.setTestOnCreate(getDefaultTestOnCreate()); + config.setTestOnBorrow(getDefaultTestOnBorrow()); + config.setTestOnReturn(getDefaultTestOnReturn()); + config.setTestWhileIdle(getDefaultTestWhileIdle()); + config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns()); + + final KeyedObjectPool tmpPool = new GenericKeyedObjectPool<>(factory, config); + factory.setPool(tmpPool); + pool = tmpPool; + } + + /** + * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. + * + * @param maxTotal + * {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. + */ + public void setMaxTotal(final int maxTotal) { + assertInitializationAllowed(); + this.maxTotal = maxTotal; + } + + @Override + protected void setupDefaults(final Connection connection, final String userName) throws SQLException { + final Boolean defaultAutoCommit = isDefaultAutoCommit(); + if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) { + connection.setAutoCommit(defaultAutoCommit); + } + + final int defaultTransactionIsolation = getDefaultTransactionIsolation(); + if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { + connection.setTransactionIsolation(defaultTransactionIsolation); + } + + final Boolean defaultReadOnly = isDefaultReadOnly(); + if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) { + connection.setReadOnly(defaultReadOnly); + } + } + + @Override + protected void toStringFields(final StringBuilder builder) { + super.toStringFields(builder); + builder.append(", maxTotal="); + builder.append(maxTotal); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/UserPassKey.java b/src/main/java/org/apache/commons/dbcp2/datasources/UserPassKey.java index cde1bf15dd..9cc6a645ce 100644 --- a/src/main/java/org/apache/commons/dbcp2/datasources/UserPassKey.java +++ b/src/main/java/org/apache/commons/dbcp2/datasources/UserPassKey.java @@ -1,115 +1,115 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.datasources; - -import java.io.Serializable; -import java.util.Objects; - -import org.apache.commons.pool2.KeyedObjectPool; - -/** - *

- * Holds a user name and password pair. Serves as a poolable object key for the {@link KeyedObjectPool} backing a - * {@link SharedPoolDataSource}. Two instances with the same user name are considered equal. This ensures that there - * will be only one keyed pool for each user in the pool. The password is used (along with the user name) by the - * {@code KeyedCPDSConnectionFactory} when creating new connections. - *

- * - *

- * {@link InstanceKeyDataSource#getConnection(String, String)} validates that the password used to create a connection - * matches the password provided by the client. - *

- * - * @since 2.0 - */ -final class UserPassKey implements Serializable { - private static final long serialVersionUID = 5142970911626584817L; - - private final CharArray name; - private final CharArray password; - - UserPassKey(final CharArray userName, final CharArray userPassword) { - this.name = userName; - this.password = userPassword; - } - - UserPassKey(final String userName) { - this(new CharArray(userName), CharArray.NULL); - } - - UserPassKey(final String userName, final char[] password) { - this(new CharArray(userName), new CharArray(password)); - } - - UserPassKey(final String userName, final String userPassword) { - this(new CharArray(userName), new CharArray(userPassword)); - } - - /** - * Only takes the user name into account. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final UserPassKey other = (UserPassKey) obj; - return Objects.equals(name, other.name); - } - - /** - * Gets the value of password. - * - * @return value of password. - */ - String getPassword() { - return password.asString(); - } - - /** - * Gets the value of password. - * - * @return value of password. - */ - char[] getPasswordCharArray() { - return password.get(); - } - - /** - * Gets the value of user name. - * - * @return value of user name. - */ - String getUserName() { - return name.asString(); - } - - /** - * Only takes the user name into account. - */ - @Override - public int hashCode() { - return Objects.hash(name); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.datasources; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.pool2.KeyedObjectPool; + +/** + *

+ * Holds a user name and password pair. Serves as a poolable object key for the {@link KeyedObjectPool} backing a + * {@link SharedPoolDataSource}. Two instances with the same user name are considered equal. This ensures that there + * will be only one keyed pool for each user in the pool. The password is used (along with the user name) by the + * {@code KeyedCPDSConnectionFactory} when creating new connections. + *

+ * + *

+ * {@link InstanceKeyDataSource#getConnection(String, String)} validates that the password used to create a connection + * matches the password provided by the client. + *

+ * + * @since 2.0 + */ +final class UserPassKey implements Serializable { + private static final long serialVersionUID = 5142970911626584817L; + + private final CharArray name; + private final CharArray password; + + UserPassKey(final CharArray userName, final CharArray userPassword) { + this.name = userName; + this.password = userPassword; + } + + UserPassKey(final String userName) { + this(new CharArray(userName), CharArray.NULL); + } + + UserPassKey(final String userName, final char[] password) { + this(new CharArray(userName), new CharArray(password)); + } + + UserPassKey(final String userName, final String userPassword) { + this(new CharArray(userName), new CharArray(userPassword)); + } + + /** + * Only takes the user name into account. + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final UserPassKey other = (UserPassKey) obj; + return Objects.equals(name, other.name); + } + + /** + * Gets the value of password. + * + * @return value of password. + */ + String getPassword() { + return password.asString(); + } + + /** + * Gets the value of password. + * + * @return value of password. + */ + char[] getPasswordCharArray() { + return password.get(); + } + + /** + * Gets the value of user name. + * + * @return value of user name. + */ + String getUserName() { + return name.asString(); + } + + /** + * Only takes the user name into account. + */ + @Override + public int hashCode() { + return Objects.hash(name); + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/DataSourceXAConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/managed/DataSourceXAConnectionFactory.java index 17b6b6b40e..a85c5ef364 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/DataSourceXAConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/DataSourceXAConnectionFactory.java @@ -1,253 +1,253 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Objects; - -import javax.sql.ConnectionEvent; -import javax.sql.ConnectionEventListener; -import javax.sql.PooledConnection; -import javax.sql.XAConnection; -import javax.sql.XADataSource; -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAResource; - -import org.apache.commons.dbcp2.Utils; - -/** - * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources. - * - * @since 2.0 - */ -public class DataSourceXAConnectionFactory implements XAConnectionFactory { - - private static final class XAConnectionEventListener implements ConnectionEventListener { - @Override - public void connectionClosed(final ConnectionEvent event) { - final PooledConnection pc = (PooledConnection) event.getSource(); - pc.removeConnectionEventListener(this); - try { - pc.close(); - } catch (final SQLException e) { - System.err.println("Failed to close XAConnection"); - e.printStackTrace(); - } - } - - @Override - public void connectionErrorOccurred(final ConnectionEvent event) { - connectionClosed(event); - } - } - - private final TransactionRegistry transactionRegistry; - private final XADataSource xaDataSource; - private String userName; - private char[] userPassword; - - /** - * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param xaDataSource - * the data source from which connections will be retrieved - * @since 2.6.0 - */ - public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource) { - this(transactionManager, xaDataSource, null, (char[]) null, null); - } - - /** - * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param xaDataSource - * the data source from which connections will be retrieved - * @param userName - * the user name used for authenticating new connections or null for unauthenticated - * @param userPassword - * the password used for authenticating new connections - */ - public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, - final String userName, final char[] userPassword) { - this(transactionManager, xaDataSource, userName, userPassword, null); - } - - /** - * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param xaDataSource - * the data source from which connections will be retrieved - * @param userName - * the user name used for authenticating new connections or null for unauthenticated - * @param userPassword - * the password used for authenticating new connections - * @param transactionSynchronizationRegistry - * register with this TransactionSynchronizationRegistry - * @since 2.6.0 - */ - public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, - final String userName, final char[] userPassword, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { - Objects.requireNonNull(transactionManager, "transactionManager"); - Objects.requireNonNull(xaDataSource, "xaDataSource"); - // We do allow the transactionSynchronizationRegistry to be null for non-app server environments - this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry); - this.xaDataSource = xaDataSource; - this.userName = userName; - this.userPassword = Utils.clone(userPassword); - } - - /** - * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param xaDataSource - * the data source from which connections will be retrieved - * @param userName - * the user name used for authenticating new connections or null for unauthenticated - * @param userPassword - * the password used for authenticating new connections - */ - public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, - final String userName, final String userPassword) { - this(transactionManager, xaDataSource, userName, Utils.toCharArray(userPassword), null); - } - - /** - * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param xaDataSource - * the data source from which connections will be retrieved - * @param transactionSynchronizationRegistry - * register with this TransactionSynchronizationRegistry - */ - public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { - this(transactionManager, xaDataSource, null, (char[]) null, transactionSynchronizationRegistry); - } - - @Override - public Connection createConnection() throws SQLException { - // create a new XAConnection - final XAConnection xaConnection; - if (userName == null) { - xaConnection = xaDataSource.getXAConnection(); - } else { - xaConnection = xaDataSource.getXAConnection(userName, Utils.toString(userPassword)); - } - // get the real connection and XAResource from the connection - final Connection connection = xaConnection.getConnection(); - final XAResource xaResource = xaConnection.getXAResource(); - // register the XA resource for the connection - transactionRegistry.registerConnection(connection, xaResource); - // The Connection we're returning is a handle on the XAConnection. - // When the pool calling us closes the Connection, we need to - // also close the XAConnection that holds the physical connection. - xaConnection.addConnectionEventListener(new XAConnectionEventListener()); - - return connection; - } - - @Override - public TransactionRegistry getTransactionRegistry() { - return transactionRegistry; - } - - /** - * Gets the user name used to authenticate new connections. - * - * @return the user name or null if unauthenticated connections are used - * @deprecated Use {@link #getUserName()}. - */ - @Deprecated - public String getUsername() { - return userName; - } - - /** - * Gets the user name used to authenticate new connections. - * - * @return the user name or null if unauthenticated connections are used - * @since 2.6.0 - */ - public String getUserName() { - return userName; - } - - /** - * Gets the user password. - * - * @return the user password. - */ - public char[] getUserPassword() { - return Utils.clone(userPassword); - } - - /** - * Gets the XA data source. - * - * @return the XA data source. - */ - public XADataSource getXaDataSource() { - return xaDataSource; - } - - /** - * Sets the password used to authenticate new connections. - * - * @param userPassword - * the password used for authenticating the connection or null for unauthenticated. - * @since 2.4.0 - */ - public void setPassword(final char[] userPassword) { - this.userPassword = Utils.clone(userPassword); - } - - /** - * Sets the password used to authenticate new connections. - * - * @param userPassword - * the password used for authenticating the connection or null for unauthenticated - */ - public void setPassword(final String userPassword) { - this.userPassword = Utils.toCharArray(userPassword); - } - - /** - * Sets the user name used to authenticate new connections. - * - * @param userName - * the user name used for authenticating the connection or null for unauthenticated - */ - public void setUsername(final String userName) { - this.userName = userName; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.PooledConnection; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAResource; + +import org.apache.commons.dbcp2.Utils; + +/** + * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources. + * + * @since 2.0 + */ +public class DataSourceXAConnectionFactory implements XAConnectionFactory { + + private static final class XAConnectionEventListener implements ConnectionEventListener { + @Override + public void connectionClosed(final ConnectionEvent event) { + final PooledConnection pc = (PooledConnection) event.getSource(); + pc.removeConnectionEventListener(this); + try { + pc.close(); + } catch (final SQLException e) { + System.err.println("Failed to close XAConnection"); + e.printStackTrace(); + } + } + + @Override + public void connectionErrorOccurred(final ConnectionEvent event) { + connectionClosed(event); + } + } + + private final TransactionRegistry transactionRegistry; + private final XADataSource xaDataSource; + private String userName; + private char[] userPassword; + + /** + * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param xaDataSource + * the data source from which connections will be retrieved + * @since 2.6.0 + */ + public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource) { + this(transactionManager, xaDataSource, null, (char[]) null, null); + } + + /** + * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param xaDataSource + * the data source from which connections will be retrieved + * @param userName + * the user name used for authenticating new connections or null for unauthenticated + * @param userPassword + * the password used for authenticating new connections + */ + public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, + final String userName, final char[] userPassword) { + this(transactionManager, xaDataSource, userName, userPassword, null); + } + + /** + * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param xaDataSource + * the data source from which connections will be retrieved + * @param userName + * the user name used for authenticating new connections or null for unauthenticated + * @param userPassword + * the password used for authenticating new connections + * @param transactionSynchronizationRegistry + * register with this TransactionSynchronizationRegistry + * @since 2.6.0 + */ + public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, + final String userName, final char[] userPassword, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { + Objects.requireNonNull(transactionManager, "transactionManager"); + Objects.requireNonNull(xaDataSource, "xaDataSource"); + // We do allow the transactionSynchronizationRegistry to be null for non-app server environments + this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry); + this.xaDataSource = xaDataSource; + this.userName = userName; + this.userPassword = Utils.clone(userPassword); + } + + /** + * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param xaDataSource + * the data source from which connections will be retrieved + * @param userName + * the user name used for authenticating new connections or null for unauthenticated + * @param userPassword + * the password used for authenticating new connections + */ + public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, + final String userName, final String userPassword) { + this(transactionManager, xaDataSource, userName, Utils.toCharArray(userPassword), null); + } + + /** + * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param xaDataSource + * the data source from which connections will be retrieved + * @param transactionSynchronizationRegistry + * register with this TransactionSynchronizationRegistry + */ + public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { + this(transactionManager, xaDataSource, null, (char[]) null, transactionSynchronizationRegistry); + } + + @Override + public Connection createConnection() throws SQLException { + // create a new XAConnection + final XAConnection xaConnection; + if (userName == null) { + xaConnection = xaDataSource.getXAConnection(); + } else { + xaConnection = xaDataSource.getXAConnection(userName, Utils.toString(userPassword)); + } + // get the real connection and XAResource from the connection + final Connection connection = xaConnection.getConnection(); + final XAResource xaResource = xaConnection.getXAResource(); + // register the XA resource for the connection + transactionRegistry.registerConnection(connection, xaResource); + // The Connection we're returning is a handle on the XAConnection. + // When the pool calling us closes the Connection, we need to + // also close the XAConnection that holds the physical connection. + xaConnection.addConnectionEventListener(new XAConnectionEventListener()); + + return connection; + } + + @Override + public TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + + /** + * Gets the user name used to authenticate new connections. + * + * @return the user name or null if unauthenticated connections are used + * @deprecated Use {@link #getUserName()}. + */ + @Deprecated + public String getUsername() { + return userName; + } + + /** + * Gets the user name used to authenticate new connections. + * + * @return the user name or null if unauthenticated connections are used + * @since 2.6.0 + */ + public String getUserName() { + return userName; + } + + /** + * Gets the user password. + * + * @return the user password. + */ + public char[] getUserPassword() { + return Utils.clone(userPassword); + } + + /** + * Gets the XA data source. + * + * @return the XA data source. + */ + public XADataSource getXaDataSource() { + return xaDataSource; + } + + /** + * Sets the password used to authenticate new connections. + * + * @param userPassword + * the password used for authenticating the connection or null for unauthenticated. + * @since 2.4.0 + */ + public void setPassword(final char[] userPassword) { + this.userPassword = Utils.clone(userPassword); + } + + /** + * Sets the password used to authenticate new connections. + * + * @param userPassword + * the password used for authenticating the connection or null for unauthenticated + */ + public void setPassword(final String userPassword) { + this.userPassword = Utils.toCharArray(userPassword); + } + + /** + * Sets the user name used to authenticate new connections. + * + * @param userName + * the user name used for authenticating the connection or null for unauthenticated + */ + public void setUsername(final String userName) { + this.userName = userName; + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/LocalXAConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/managed/LocalXAConnectionFactory.java index 248c5cff44..189fa14bea 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/LocalXAConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/LocalXAConnectionFactory.java @@ -1,390 +1,390 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Objects; - -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; - -import org.apache.commons.dbcp2.ConnectionFactory; - -/** - * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions. A non-XA connection - * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement - * the 2-phase protocol. - * - * @since 2.0 - */ -public class LocalXAConnectionFactory implements XAConnectionFactory { - - /** - * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started the connection - * auto-commit is turned off. When the connection is committed or rolled back, the commit or rollback method is - * called on the connection and then the original auto-commit value is restored. - *

- * The LocalXAResource also respects the connection read-only setting. If the connection is read-only the commit - * method will not be called, and the prepare method returns the XA_RDONLY. - *

- *

- * It is assumed that the wrapper around a managed connection disables the setAutoCommit(), commit(), rollback() and - * setReadOnly() methods while a transaction is in progress. - *

- * - * @since 2.0 - */ - protected static class LocalXAResource implements XAResource { - private static final Xid[] EMPTY_XID_ARRAY = {}; - private final Connection connection; - private Xid currentXid; // @GuardedBy("this") - private boolean originalAutoCommit; // @GuardedBy("this") - - /** - * Constructs a new instance for a given connection. - * - * @param localTransaction A connection. - */ - public LocalXAResource(final Connection localTransaction) { - this.connection = localTransaction; - } - - private Xid checkCurrentXid() throws XAException { - if (this.currentXid == null) { - throw new XAException("There is no current transaction"); - } - return currentXid; - } - - /** - * Commits the transaction and restores the original auto commit setting. - * - * @param xid - * the id of the transaction branch for this connection - * @param flag - * ignored - * @throws XAException - * if connection.commit() throws an SQLException - */ - @Override - public synchronized void commit(final Xid xid, final boolean flag) throws XAException { - Objects.requireNonNull(xid, "xid"); - if (!checkCurrentXid().equals(xid)) { - throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); - } - - try { - // make sure the connection isn't already closed - if (connection.isClosed()) { - throw new XAException("Connection is closed"); - } - - // A read only connection should not be committed - if (!connection.isReadOnly()) { - connection.commit(); - } - } catch (final SQLException e) { - throw (XAException) new XAException().initCause(e); - } finally { - try { - connection.setAutoCommit(originalAutoCommit); - } catch (final SQLException ignored) { - // ignored - } - this.currentXid = null; - } - } - - /** - * This method does nothing. - * - * @param xid - * the id of the transaction branch for this connection - * @param flag - * ignored - * @throws XAException - * if the connection is already enlisted in another transaction - */ - @Override - public synchronized void end(final Xid xid, final int flag) throws XAException { - Objects.requireNonNull(xid, "xid"); - if (!checkCurrentXid().equals(xid)) { - throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); - } - - // This notification tells us that the application server is done using this - // connection for the time being. The connection is still associated with an - // open transaction, so we must still wait for the commit or rollback method - } - - /** - * Clears the currently associated transaction if it is the specified xid. - * - * @param xid - * the id of the transaction to forget - */ - @Override - public synchronized void forget(final Xid xid) { - if (xid != null && xid.equals(currentXid)) { - currentXid = null; - } - } - - /** - * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection. - * - * @return always 0 - */ - @Override - public int getTransactionTimeout() { - return 0; - } - - /** - * Gets the current xid of the transaction branch associated with this XAResource. - * - * @return the current xid of the transaction branch associated with this XAResource. - */ - public synchronized Xid getXid() { - return currentXid; - } - - /** - * Returns true if the specified XAResource == this XAResource. - * - * @param xaResource - * the XAResource to test - * @return true if the specified XAResource == this XAResource; false otherwise - */ - @Override - public boolean isSameRM(final XAResource xaResource) { - return this == xaResource; - } - - /** - * This method does nothing since the LocalXAConnection does not support two-phase-commit. This method will - * return XAResource.XA_RDONLY if the connection isReadOnly(). This assumes that the physical connection is - * wrapped with a proxy that prevents an application from changing the read-only flag while enrolled in a - * transaction. - * - * @param xid - * the id of the transaction branch for this connection - * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise - */ - @Override - public synchronized int prepare(final Xid xid) { - // if the connection is read-only, then the resource is read-only - // NOTE: this assumes that the outer proxy throws an exception when application code - // attempts to set this in a transaction - try { - if (connection.isReadOnly()) { - // update the auto commit flag - connection.setAutoCommit(originalAutoCommit); - - // tell the transaction manager we are read only - return XA_RDONLY; - } - } catch (final SQLException ignored) { - // no big deal - } - - // this is a local (one phase) only connection, so we can't prepare - return XA_OK; - } - - /** - * Always returns a zero length Xid array. The LocalXAConnectionFactory cannot support recovery, so no xids - * will ever be found. - * - * @param flag - * ignored since recovery is not supported - * @return always a zero length Xid array. - */ - @Override - public Xid[] recover(final int flag) { - return EMPTY_XID_ARRAY; - } - - /** - * Rolls back the transaction and restores the original auto commit setting. - * - * @param xid - * the id of the transaction branch for this connection - * @throws XAException - * if connection.rollback() throws an SQLException - */ - @Override - public synchronized void rollback(final Xid xid) throws XAException { - Objects.requireNonNull(xid, "xid"); - if (!checkCurrentXid().equals(xid)) { - throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); - } - - try { - connection.rollback(); - } catch (final SQLException e) { - throw (XAException) new XAException().initCause(e); - } finally { - try { - connection.setAutoCommit(originalAutoCommit); - } catch (final SQLException ignored) { - // Ignored. - } - this.currentXid = null; - } - } - - /** - * Always returns false since we have no way to set a transaction timeout on a JDBC connection. - * - * @param transactionTimeout - * ignored since we have no way to set a transaction timeout on a JDBC connection - * @return always false - */ - @Override - public boolean setTransactionTimeout(final int transactionTimeout) { - return false; - } - - /** - * Signals that a connection has been enrolled in a transaction. This method saves off the current auto - * commit flag, and then disables auto commit. The original auto commit setting is restored when the transaction - * completes. - * - * @param xid - * the id of the transaction branch for this connection - * @param flag - * either XAResource.TMNOFLAGS or XAResource.TMRESUME - * @throws XAException - * if the connection is already enlisted in another transaction, or if auto-commit could not be - * disabled - */ - @Override - public synchronized void start(final Xid xid, final int flag) throws XAException { - if (flag == TMNOFLAGS) { - // first time in this transaction - - // make sure we aren't already in another tx - if (this.currentXid != null) { - throw new XAException("Already enlisted in another transaction with xid " + xid); - } - - // save off the current auto commit flag, so it can be restored after the transaction completes - try { - originalAutoCommit = connection.getAutoCommit(); - } catch (final SQLException ignored) { - // no big deal, just assume it was off - originalAutoCommit = true; - } - - // update the auto commit flag - try { - connection.setAutoCommit(false); - } catch (final SQLException e) { - throw (XAException) new XAException("Count not turn off auto commit for a XA transaction") - .initCause(e); - } - - this.currentXid = xid; - } else if (flag == TMRESUME) { - if (!xid.equals(this.currentXid)) { - throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid - + ", but was " + xid); - } - } else { - throw new XAException("Unknown start flag " + flag); - } - } - } - private final TransactionRegistry transactionRegistry; - - private final ConnectionFactory connectionFactory; - - /** - * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param connectionFactory - * the connection factory from which connections will be retrieved - */ - public LocalXAConnectionFactory(final TransactionManager transactionManager, - final ConnectionFactory connectionFactory) { - this(transactionManager, null, connectionFactory); - } - - /** - * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections. - * The connections are enlisted into transactions using the specified transaction manager. - * - * @param transactionManager - * the transaction manager in which connections will be enlisted - * @param transactionSynchronizationRegistry - * the optional TSR to register synchronizations with - * @param connectionFactory - * the connection factory from which connections will be retrieved - * @since 2.8.0 - */ - public LocalXAConnectionFactory(final TransactionManager transactionManager, - final TransactionSynchronizationRegistry transactionSynchronizationRegistry, - final ConnectionFactory connectionFactory) { - Objects.requireNonNull(transactionManager, "transactionManager"); - Objects.requireNonNull(connectionFactory, "connectionFactory"); - this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry); - this.connectionFactory = connectionFactory; - } - - @Override - public Connection createConnection() throws SQLException { - // create a new connection - final Connection connection = connectionFactory.createConnection(); - - // create a XAResource to manage the connection during XA transactions - final XAResource xaResource = new LocalXAResource(connection); - - // register the XA resource for the connection - transactionRegistry.registerConnection(connection, xaResource); - - return connection; - } - - /** - * Gets the connection factory. - * - * @return The connection factory. - * @since 2.6.0 - */ - public ConnectionFactory getConnectionFactory() { - return connectionFactory; - } - - /** - * Gets the transaction registry. - * - * @return The transaction registry. - */ - @Override - public TransactionRegistry getTransactionRegistry() { - return transactionRegistry; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import org.apache.commons.dbcp2.ConnectionFactory; + +/** + * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions. A non-XA connection + * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement + * the 2-phase protocol. + * + * @since 2.0 + */ +public class LocalXAConnectionFactory implements XAConnectionFactory { + + /** + * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started the connection + * auto-commit is turned off. When the connection is committed or rolled back, the commit or rollback method is + * called on the connection and then the original auto-commit value is restored. + *

+ * The LocalXAResource also respects the connection read-only setting. If the connection is read-only the commit + * method will not be called, and the prepare method returns the XA_RDONLY. + *

+ *

+ * It is assumed that the wrapper around a managed connection disables the setAutoCommit(), commit(), rollback() and + * setReadOnly() methods while a transaction is in progress. + *

+ * + * @since 2.0 + */ + protected static class LocalXAResource implements XAResource { + private static final Xid[] EMPTY_XID_ARRAY = {}; + private final Connection connection; + private Xid currentXid; // @GuardedBy("this") + private boolean originalAutoCommit; // @GuardedBy("this") + + /** + * Constructs a new instance for a given connection. + * + * @param localTransaction A connection. + */ + public LocalXAResource(final Connection localTransaction) { + this.connection = localTransaction; + } + + private Xid checkCurrentXid() throws XAException { + if (this.currentXid == null) { + throw new XAException("There is no current transaction"); + } + return currentXid; + } + + /** + * Commits the transaction and restores the original auto commit setting. + * + * @param xid + * the id of the transaction branch for this connection + * @param flag + * ignored + * @throws XAException + * if connection.commit() throws an SQLException + */ + @Override + public synchronized void commit(final Xid xid, final boolean flag) throws XAException { + Objects.requireNonNull(xid, "xid"); + if (!checkCurrentXid().equals(xid)) { + throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); + } + + try { + // make sure the connection isn't already closed + if (connection.isClosed()) { + throw new XAException("Connection is closed"); + } + + // A read only connection should not be committed + if (!connection.isReadOnly()) { + connection.commit(); + } + } catch (final SQLException e) { + throw (XAException) new XAException().initCause(e); + } finally { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (final SQLException ignored) { + // ignored + } + this.currentXid = null; + } + } + + /** + * This method does nothing. + * + * @param xid + * the id of the transaction branch for this connection + * @param flag + * ignored + * @throws XAException + * if the connection is already enlisted in another transaction + */ + @Override + public synchronized void end(final Xid xid, final int flag) throws XAException { + Objects.requireNonNull(xid, "xid"); + if (!checkCurrentXid().equals(xid)) { + throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); + } + + // This notification tells us that the application server is done using this + // connection for the time being. The connection is still associated with an + // open transaction, so we must still wait for the commit or rollback method + } + + /** + * Clears the currently associated transaction if it is the specified xid. + * + * @param xid + * the id of the transaction to forget + */ + @Override + public synchronized void forget(final Xid xid) { + if (xid != null && xid.equals(currentXid)) { + currentXid = null; + } + } + + /** + * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection. + * + * @return always 0 + */ + @Override + public int getTransactionTimeout() { + return 0; + } + + /** + * Gets the current xid of the transaction branch associated with this XAResource. + * + * @return the current xid of the transaction branch associated with this XAResource. + */ + public synchronized Xid getXid() { + return currentXid; + } + + /** + * Returns true if the specified XAResource == this XAResource. + * + * @param xaResource + * the XAResource to test + * @return true if the specified XAResource == this XAResource; false otherwise + */ + @Override + public boolean isSameRM(final XAResource xaResource) { + return this == xaResource; + } + + /** + * This method does nothing since the LocalXAConnection does not support two-phase-commit. This method will + * return XAResource.XA_RDONLY if the connection isReadOnly(). This assumes that the physical connection is + * wrapped with a proxy that prevents an application from changing the read-only flag while enrolled in a + * transaction. + * + * @param xid + * the id of the transaction branch for this connection + * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise + */ + @Override + public synchronized int prepare(final Xid xid) { + // if the connection is read-only, then the resource is read-only + // NOTE: this assumes that the outer proxy throws an exception when application code + // attempts to set this in a transaction + try { + if (connection.isReadOnly()) { + // update the auto commit flag + connection.setAutoCommit(originalAutoCommit); + + // tell the transaction manager we are read only + return XA_RDONLY; + } + } catch (final SQLException ignored) { + // no big deal + } + + // this is a local (one phase) only connection, so we can't prepare + return XA_OK; + } + + /** + * Always returns a zero length Xid array. The LocalXAConnectionFactory cannot support recovery, so no xids + * will ever be found. + * + * @param flag + * ignored since recovery is not supported + * @return always a zero length Xid array. + */ + @Override + public Xid[] recover(final int flag) { + return EMPTY_XID_ARRAY; + } + + /** + * Rolls back the transaction and restores the original auto commit setting. + * + * @param xid + * the id of the transaction branch for this connection + * @throws XAException + * if connection.rollback() throws an SQLException + */ + @Override + public synchronized void rollback(final Xid xid) throws XAException { + Objects.requireNonNull(xid, "xid"); + if (!checkCurrentXid().equals(xid)) { + throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid); + } + + try { + connection.rollback(); + } catch (final SQLException e) { + throw (XAException) new XAException().initCause(e); + } finally { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (final SQLException ignored) { + // Ignored. + } + this.currentXid = null; + } + } + + /** + * Always returns false since we have no way to set a transaction timeout on a JDBC connection. + * + * @param transactionTimeout + * ignored since we have no way to set a transaction timeout on a JDBC connection + * @return always false + */ + @Override + public boolean setTransactionTimeout(final int transactionTimeout) { + return false; + } + + /** + * Signals that a connection has been enrolled in a transaction. This method saves off the current auto + * commit flag, and then disables auto commit. The original auto commit setting is restored when the transaction + * completes. + * + * @param xid + * the id of the transaction branch for this connection + * @param flag + * either XAResource.TMNOFLAGS or XAResource.TMRESUME + * @throws XAException + * if the connection is already enlisted in another transaction, or if auto-commit could not be + * disabled + */ + @Override + public synchronized void start(final Xid xid, final int flag) throws XAException { + if (flag == TMNOFLAGS) { + // first time in this transaction + + // make sure we aren't already in another tx + if (this.currentXid != null) { + throw new XAException("Already enlisted in another transaction with xid " + xid); + } + + // save off the current auto commit flag, so it can be restored after the transaction completes + try { + originalAutoCommit = connection.getAutoCommit(); + } catch (final SQLException ignored) { + // no big deal, just assume it was off + originalAutoCommit = true; + } + + // update the auto commit flag + try { + connection.setAutoCommit(false); + } catch (final SQLException e) { + throw (XAException) new XAException("Count not turn off auto commit for a XA transaction") + .initCause(e); + } + + this.currentXid = xid; + } else if (flag == TMRESUME) { + if (!xid.equals(this.currentXid)) { + throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid + + ", but was " + xid); + } + } else { + throw new XAException("Unknown start flag " + flag); + } + } + } + private final TransactionRegistry transactionRegistry; + + private final ConnectionFactory connectionFactory; + + /** + * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param connectionFactory + * the connection factory from which connections will be retrieved + */ + public LocalXAConnectionFactory(final TransactionManager transactionManager, + final ConnectionFactory connectionFactory) { + this(transactionManager, null, connectionFactory); + } + + /** + * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param transactionSynchronizationRegistry + * the optional TSR to register synchronizations with + * @param connectionFactory + * the connection factory from which connections will be retrieved + * @since 2.8.0 + */ + public LocalXAConnectionFactory(final TransactionManager transactionManager, + final TransactionSynchronizationRegistry transactionSynchronizationRegistry, + final ConnectionFactory connectionFactory) { + Objects.requireNonNull(transactionManager, "transactionManager"); + Objects.requireNonNull(connectionFactory, "connectionFactory"); + this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry); + this.connectionFactory = connectionFactory; + } + + @Override + public Connection createConnection() throws SQLException { + // create a new connection + final Connection connection = connectionFactory.createConnection(); + + // create a XAResource to manage the connection during XA transactions + final XAResource xaResource = new LocalXAResource(connection); + + // register the XA resource for the connection + transactionRegistry.registerConnection(connection, xaResource); + + return connection; + } + + /** + * Gets the connection factory. + * + * @return The connection factory. + * @since 2.6.0 + */ + public ConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + /** + * Gets the transaction registry. + * + * @return The transaction registry. + */ + @Override + public TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java b/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java index a5a72d1a28..ae4e1a4335 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java @@ -1,332 +1,332 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.dbcp2.DelegatingConnection; -import org.apache.commons.pool2.ObjectPool; - -/** - * ManagedConnection is responsible for managing a database connection in a transactional environment (typically called - * "Container Managed"). A managed connection operates like any other connection when no global transaction (a.k.a. XA - * transaction or JTA Transaction) is in progress. When a global transaction is active a single physical connection to - * the database is used by all ManagedConnections accessed in the scope of the transaction. Connection sharing means - * that all data access during a transaction has a consistent view of the database. When the global transaction is - * committed or rolled back the enlisted connections are committed or rolled back. Typically, upon transaction - * completion, a connection returns to the auto commit setting in effect before being enlisted in the transaction, but - * some vendors do not properly implement this. - *

- * When enlisted in a transaction the setAutoCommit(), commit(), rollback(), and setReadOnly() methods throw a - * SQLException. This is necessary to assure that the transaction completes as a single unit. - *

- * - * @param - * the Connection type - * - * @since 2.0 - */ -public class ManagedConnection extends DelegatingConnection { - - /** - * Delegates to {@link ManagedConnection#transactionComplete()} for transaction completion events. - * - * @since 2.0 - */ - protected class CompletionListener implements TransactionContextListener { - @Override - public void afterCompletion(final TransactionContext completedContext, final boolean committed) { - if (completedContext == transactionContext) { - transactionComplete(); - } - } - } - - private final ObjectPool pool; - private final TransactionRegistry transactionRegistry; - private final boolean accessToUnderlyingConnectionAllowed; - private TransactionContext transactionContext; - private boolean isSharedConnection; - private final Lock lock; - - /** - * Constructs a new instance responsible for managing a database connection in a transactional environment. - * - * @param pool - * The connection pool. - * @param transactionRegistry - * The transaction registry. - * @param accessToUnderlyingConnectionAllowed - * Whether or not to allow access to the underlying Connection. - * @throws SQLException - * Thrown when there is problem managing transactions. - */ - public ManagedConnection(final ObjectPool pool, final TransactionRegistry transactionRegistry, - final boolean accessToUnderlyingConnectionAllowed) throws SQLException { - super(null); - this.pool = pool; - this.transactionRegistry = transactionRegistry; - this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; - this.lock = new ReentrantLock(); - updateTransactionStatus(); - } - - @Override - protected void checkOpen() throws SQLException { - super.checkOpen(); - updateTransactionStatus(); - } - - @Override - public void close() throws SQLException { - if (!isClosedInternal()) { - // Don't actually close the connection if in a transaction. The - // connection will be closed by the transactionComplete method. - // - // DBCP-484 we need to make sure setClosedInternal(true) being - // invoked if transactionContext is not null as this value will - // be modified by the transactionComplete method which could run - // in the different thread with the transaction calling back. - lock.lock(); - try { - if (transactionContext == null || transactionContext.isTransactionComplete()) { - super.close(); - } - } finally { - try { - setClosedInternal(true); - } finally { - lock.unlock(); - } - } - } - } - - @Override - public void commit() throws SQLException { - if (transactionContext != null) { - throw new SQLException("Commit cannot be set while enrolled in a transaction"); - } - super.commit(); - } - - @Override - public C getDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return getDelegateInternal(); - } - return null; - } - - // - // The following methods can't be used while enlisted in a transaction - // - - @Override - public Connection getInnermostDelegate() { - if (isAccessToUnderlyingConnectionAllowed()) { - return super.getInnermostDelegateInternal(); - } - return null; - } - - /** - * Gets the transaction context. - * - * @return The transaction context. - * @since 2.6.0 - */ - public TransactionContext getTransactionContext() { - return transactionContext; - } - - /** - * Gets the transaction registry. - * - * @return The transaction registry. - * @since 2.6.0 - */ - public TransactionRegistry getTransactionRegistry() { - return transactionRegistry; - } - - /** - * If false, getDelegate() and getInnermostDelegate() will return null. - * - * @return if false, getDelegate() and getInnermostDelegate() will return null - */ - public boolean isAccessToUnderlyingConnectionAllowed() { - return accessToUnderlyingConnectionAllowed; - } - - @Override - public void rollback() throws SQLException { - if (transactionContext != null) { - throw new SQLException("Commit cannot be set while enrolled in a transaction"); - } - super.rollback(); - } - - @Override - public void setAutoCommit(final boolean autoCommit) throws SQLException { - if (transactionContext != null) { - throw new SQLException("Auto-commit cannot be set while enrolled in a transaction"); - } - super.setAutoCommit(autoCommit); - } - - @Override - public void setReadOnly(final boolean readOnly) throws SQLException { - if (transactionContext != null) { - throw new SQLException("Read-only cannot be set while enrolled in a transaction"); - } - super.setReadOnly(readOnly); - } - - /** - * Completes the transaction. - */ - protected void transactionComplete() { - lock.lock(); - try { - transactionContext.completeTransaction(); - } finally { - lock.unlock(); - } - - // If we were using a shared connection, clear the reference now that - // the transaction has completed - if (isSharedConnection) { - setDelegate(null); - isSharedConnection = false; - } - - // autoCommit may have been changed directly on the underlying connection - clearCachedState(); - - // If this connection was closed during the transaction and there is - // still a delegate present close it - final Connection delegate = getDelegateInternal(); - if (isClosedInternal() && delegate != null) { - try { - setDelegate(null); - - if (!delegate.isClosed()) { - delegate.close(); - } - } catch (final SQLException ignored) { - // Not a whole lot we can do here as connection is closed - // and this is a transaction callback so there is no - // way to report the error. - } - } - } - - private void updateTransactionStatus() throws SQLException { - // if there is an active transaction context, assure the transaction context hasn't changed - if (transactionContext != null && !transactionContext.isTransactionComplete()) { - if (transactionContext.isActive()) { - if (transactionContext != transactionRegistry.getActiveTransactionContext()) { - throw new SQLException("Connection cannot be used while enlisted in another transaction"); - } - return; - } - // transaction should have been cleared up by TransactionContextListener, but in - // rare cases another lister could have registered which uses the connection before - // our listener is called. In that rare case, trigger the transaction complete call now - transactionComplete(); - } - - // the existing transaction context ended (or we didn't have one), get the active transaction context - transactionContext = transactionRegistry.getActiveTransactionContext(); - - // if there is an active transaction context, and it already has a shared connection, use it - if (transactionContext != null && transactionContext.getSharedConnection() != null) { - // A connection for the connection factory has already been enrolled - // in the transaction, replace our delegate with the enrolled connection - - // return current connection to the pool - @SuppressWarnings("resource") - final C connection = getDelegateInternal(); - setDelegate(null); - if (connection != null && transactionContext.getSharedConnection() != connection) { - try { - pool.returnObject(connection); - } catch (final Exception e) { - // whatever... try to invalidate the connection - try { - pool.invalidateObject(connection); - } catch (final Exception ignored) { - // no big deal - } - } - } - - // add a listener to the transaction context - transactionContext.addTransactionContextListener(new CompletionListener()); - - // Set our delegate to the shared connection. Note that this will - // always be of type C since it has been shared by another - // connection from the same pool. - @SuppressWarnings("unchecked") - final C shared = (C) transactionContext.getSharedConnection(); - setDelegate(shared); - - // remember that we are using a shared connection, so it can be cleared after the - // transaction completes - isSharedConnection = true; - } else { - C connection = getDelegateInternal(); - // if our delegate is null, create one - if (connection == null) { - try { - // borrow a new connection from the pool - connection = pool.borrowObject(); - setDelegate(connection); - } catch (final Exception e) { - throw new SQLException("Unable to acquire a new connection from the pool", e); - } - } - - // if we have a transaction, out delegate becomes the shared delegate - if (transactionContext != null) { - // add a listener to the transaction context - transactionContext.addTransactionContextListener(new CompletionListener()); - - // register our connection as the shared connection - try { - transactionContext.setSharedConnection(connection); - } catch (final SQLException e) { - // transaction is hosed - transactionContext = null; - try { - pool.invalidateObject(connection); - } catch (final Exception ignored) { - // we are try but no luck - } - throw e; - } - } - } - // autoCommit may have been changed directly on the underlying - // connection - clearCachedState(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.dbcp2.DelegatingConnection; +import org.apache.commons.pool2.ObjectPool; + +/** + * ManagedConnection is responsible for managing a database connection in a transactional environment (typically called + * "Container Managed"). A managed connection operates like any other connection when no global transaction (a.k.a. XA + * transaction or JTA Transaction) is in progress. When a global transaction is active a single physical connection to + * the database is used by all ManagedConnections accessed in the scope of the transaction. Connection sharing means + * that all data access during a transaction has a consistent view of the database. When the global transaction is + * committed or rolled back the enlisted connections are committed or rolled back. Typically, upon transaction + * completion, a connection returns to the auto commit setting in effect before being enlisted in the transaction, but + * some vendors do not properly implement this. + *

+ * When enlisted in a transaction the setAutoCommit(), commit(), rollback(), and setReadOnly() methods throw a + * SQLException. This is necessary to assure that the transaction completes as a single unit. + *

+ * + * @param + * the Connection type + * + * @since 2.0 + */ +public class ManagedConnection extends DelegatingConnection { + + /** + * Delegates to {@link ManagedConnection#transactionComplete()} for transaction completion events. + * + * @since 2.0 + */ + protected class CompletionListener implements TransactionContextListener { + @Override + public void afterCompletion(final TransactionContext completedContext, final boolean committed) { + if (completedContext == transactionContext) { + transactionComplete(); + } + } + } + + private final ObjectPool pool; + private final TransactionRegistry transactionRegistry; + private final boolean accessToUnderlyingConnectionAllowed; + private TransactionContext transactionContext; + private boolean isSharedConnection; + private final Lock lock; + + /** + * Constructs a new instance responsible for managing a database connection in a transactional environment. + * + * @param pool + * The connection pool. + * @param transactionRegistry + * The transaction registry. + * @param accessToUnderlyingConnectionAllowed + * Whether or not to allow access to the underlying Connection. + * @throws SQLException + * Thrown when there is problem managing transactions. + */ + public ManagedConnection(final ObjectPool pool, final TransactionRegistry transactionRegistry, + final boolean accessToUnderlyingConnectionAllowed) throws SQLException { + super(null); + this.pool = pool; + this.transactionRegistry = transactionRegistry; + this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; + this.lock = new ReentrantLock(); + updateTransactionStatus(); + } + + @Override + protected void checkOpen() throws SQLException { + super.checkOpen(); + updateTransactionStatus(); + } + + @Override + public void close() throws SQLException { + if (!isClosedInternal()) { + // Don't actually close the connection if in a transaction. The + // connection will be closed by the transactionComplete method. + // + // DBCP-484 we need to make sure setClosedInternal(true) being + // invoked if transactionContext is not null as this value will + // be modified by the transactionComplete method which could run + // in the different thread with the transaction calling back. + lock.lock(); + try { + if (transactionContext == null || transactionContext.isTransactionComplete()) { + super.close(); + } + } finally { + try { + setClosedInternal(true); + } finally { + lock.unlock(); + } + } + } + } + + @Override + public void commit() throws SQLException { + if (transactionContext != null) { + throw new SQLException("Commit cannot be set while enrolled in a transaction"); + } + super.commit(); + } + + @Override + public C getDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return getDelegateInternal(); + } + return null; + } + + // + // The following methods can't be used while enlisted in a transaction + // + + @Override + public Connection getInnermostDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return super.getInnermostDelegateInternal(); + } + return null; + } + + /** + * Gets the transaction context. + * + * @return The transaction context. + * @since 2.6.0 + */ + public TransactionContext getTransactionContext() { + return transactionContext; + } + + /** + * Gets the transaction registry. + * + * @return The transaction registry. + * @since 2.6.0 + */ + public TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + + /** + * If false, getDelegate() and getInnermostDelegate() will return null. + * + * @return if false, getDelegate() and getInnermostDelegate() will return null + */ + public boolean isAccessToUnderlyingConnectionAllowed() { + return accessToUnderlyingConnectionAllowed; + } + + @Override + public void rollback() throws SQLException { + if (transactionContext != null) { + throw new SQLException("Commit cannot be set while enrolled in a transaction"); + } + super.rollback(); + } + + @Override + public void setAutoCommit(final boolean autoCommit) throws SQLException { + if (transactionContext != null) { + throw new SQLException("Auto-commit cannot be set while enrolled in a transaction"); + } + super.setAutoCommit(autoCommit); + } + + @Override + public void setReadOnly(final boolean readOnly) throws SQLException { + if (transactionContext != null) { + throw new SQLException("Read-only cannot be set while enrolled in a transaction"); + } + super.setReadOnly(readOnly); + } + + /** + * Completes the transaction. + */ + protected void transactionComplete() { + lock.lock(); + try { + transactionContext.completeTransaction(); + } finally { + lock.unlock(); + } + + // If we were using a shared connection, clear the reference now that + // the transaction has completed + if (isSharedConnection) { + setDelegate(null); + isSharedConnection = false; + } + + // autoCommit may have been changed directly on the underlying connection + clearCachedState(); + + // If this connection was closed during the transaction and there is + // still a delegate present close it + final Connection delegate = getDelegateInternal(); + if (isClosedInternal() && delegate != null) { + try { + setDelegate(null); + + if (!delegate.isClosed()) { + delegate.close(); + } + } catch (final SQLException ignored) { + // Not a whole lot we can do here as connection is closed + // and this is a transaction callback so there is no + // way to report the error. + } + } + } + + private void updateTransactionStatus() throws SQLException { + // if there is an active transaction context, assure the transaction context hasn't changed + if (transactionContext != null && !transactionContext.isTransactionComplete()) { + if (transactionContext.isActive()) { + if (transactionContext != transactionRegistry.getActiveTransactionContext()) { + throw new SQLException("Connection cannot be used while enlisted in another transaction"); + } + return; + } + // transaction should have been cleared up by TransactionContextListener, but in + // rare cases another lister could have registered which uses the connection before + // our listener is called. In that rare case, trigger the transaction complete call now + transactionComplete(); + } + + // the existing transaction context ended (or we didn't have one), get the active transaction context + transactionContext = transactionRegistry.getActiveTransactionContext(); + + // if there is an active transaction context, and it already has a shared connection, use it + if (transactionContext != null && transactionContext.getSharedConnection() != null) { + // A connection for the connection factory has already been enrolled + // in the transaction, replace our delegate with the enrolled connection + + // return current connection to the pool + @SuppressWarnings("resource") + final C connection = getDelegateInternal(); + setDelegate(null); + if (connection != null && transactionContext.getSharedConnection() != connection) { + try { + pool.returnObject(connection); + } catch (final Exception e) { + // whatever... try to invalidate the connection + try { + pool.invalidateObject(connection); + } catch (final Exception ignored) { + // no big deal + } + } + } + + // add a listener to the transaction context + transactionContext.addTransactionContextListener(new CompletionListener()); + + // Set our delegate to the shared connection. Note that this will + // always be of type C since it has been shared by another + // connection from the same pool. + @SuppressWarnings("unchecked") + final C shared = (C) transactionContext.getSharedConnection(); + setDelegate(shared); + + // remember that we are using a shared connection, so it can be cleared after the + // transaction completes + isSharedConnection = true; + } else { + C connection = getDelegateInternal(); + // if our delegate is null, create one + if (connection == null) { + try { + // borrow a new connection from the pool + connection = pool.borrowObject(); + setDelegate(connection); + } catch (final Exception e) { + throw new SQLException("Unable to acquire a new connection from the pool", e); + } + } + + // if we have a transaction, out delegate becomes the shared delegate + if (transactionContext != null) { + // add a listener to the transaction context + transactionContext.addTransactionContextListener(new CompletionListener()); + + // register our connection as the shared connection + try { + transactionContext.setSharedConnection(connection); + } catch (final SQLException e) { + // transaction is hosed + transactionContext = null; + try { + pool.invalidateObject(connection); + } catch (final Exception ignored) { + // we are try but no luck + } + throw e; + } + } + } + // autoCommit may have been changed directly on the underlying + // connection + clearCachedState(); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java index 9dc80ccb29..b218f326b7 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java @@ -1,113 +1,113 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Duration; - -import javax.management.ObjectName; - -import org.apache.commons.dbcp2.Constants; -import org.apache.commons.dbcp2.DelegatingPreparedStatement; -import org.apache.commons.dbcp2.PStmtKey; -import org.apache.commons.dbcp2.PoolableConnection; -import org.apache.commons.dbcp2.PoolableConnectionFactory; -import org.apache.commons.dbcp2.PoolingConnection; -import org.apache.commons.pool2.KeyedObjectPool; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.apache.commons.pool2.impl.GenericKeyedObjectPool; -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; - -/** - * A {@link PoolableConnectionFactory} that creates {@link PoolableManagedConnection}s. - * - * @since 2.0 - */ -public class PoolableManagedConnectionFactory extends PoolableConnectionFactory { - - /** Transaction registry associated with connections created by this factory */ - private final TransactionRegistry transactionRegistry; - - /** - * Creates a PoolableManagedConnectionFactory and attach it to a connection pool. - * - * @param connFactory - * XAConnectionFactory - * @param dataSourceJmxName - * The data source name. - */ - public PoolableManagedConnectionFactory(final XAConnectionFactory connFactory, final ObjectName dataSourceJmxName) { - super(connFactory, dataSourceJmxName); - this.transactionRegistry = connFactory.getTransactionRegistry(); - } - - /** - * Gets the transaction registry. - * - * @return The transaction registry. - * @since 2.6.0 - */ - public TransactionRegistry getTransactionRegistry() { - return transactionRegistry; - } - - /** - * Uses the configured XAConnectionFactory to create a {@link PoolableManagedConnection}. Throws - * {@code IllegalStateException} if the connection factory returns null. Also initializes the connection using - * configured initialization SQL (if provided) and sets up a prepared statement pool associated with the - * PoolableManagedConnection if statement pooling is enabled. - */ - @SuppressWarnings("resource") // Connection is released elsewhere. - @Override - public synchronized PooledObject makeObject() throws SQLException { - Connection conn = getConnectionFactory().createConnection(); - if (conn == null) { - throw new IllegalStateException("Connection factory returned null from createConnection"); - } - initializeConnection(conn); - if (getPoolStatements()) { - conn = new PoolingConnection(conn); - final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); - config.setMaxTotalPerKey(-1); - config.setBlockWhenExhausted(false); - config.setMaxWait(Duration.ZERO); - config.setMaxIdlePerKey(1); - config.setMaxTotal(getMaxOpenPreparedStatements()); - final ObjectName dataSourceJmxName = getDataSourceJmxName(); - final long connIndex = getConnectionIndex().getAndIncrement(); - if (dataSourceJmxName != null) { - final StringBuilder base = new StringBuilder(dataSourceJmxName.toString()); - base.append(Constants.JMX_CONNECTION_BASE_EXT); - base.append(connIndex); - config.setJmxNameBase(base.toString()); - config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); - } else { - config.setJmxEnabled(false); - } - final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>( - (PoolingConnection) conn, config); - ((PoolingConnection) conn).setStatementPool(stmtPool); - ((PoolingConnection) conn).setCacheState(getCacheState()); - } - final PoolableManagedConnection pmc = new PoolableManagedConnection(transactionRegistry, conn, getPool(), - getDisconnectionSqlCodes(), getDisconnectionIgnoreSqlCodes(), isFastFailValidation()); - pmc.setCacheState(getCacheState()); - return new DefaultPooledObject<>(pmc); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; + +import javax.management.ObjectName; + +import org.apache.commons.dbcp2.Constants; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.PStmtKey; +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.dbcp2.PoolingConnection; +import org.apache.commons.pool2.KeyedObjectPool; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + * A {@link PoolableConnectionFactory} that creates {@link PoolableManagedConnection}s. + * + * @since 2.0 + */ +public class PoolableManagedConnectionFactory extends PoolableConnectionFactory { + + /** Transaction registry associated with connections created by this factory */ + private final TransactionRegistry transactionRegistry; + + /** + * Creates a PoolableManagedConnectionFactory and attach it to a connection pool. + * + * @param connFactory + * XAConnectionFactory + * @param dataSourceJmxName + * The data source name. + */ + public PoolableManagedConnectionFactory(final XAConnectionFactory connFactory, final ObjectName dataSourceJmxName) { + super(connFactory, dataSourceJmxName); + this.transactionRegistry = connFactory.getTransactionRegistry(); + } + + /** + * Gets the transaction registry. + * + * @return The transaction registry. + * @since 2.6.0 + */ + public TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + + /** + * Uses the configured XAConnectionFactory to create a {@link PoolableManagedConnection}. Throws + * {@code IllegalStateException} if the connection factory returns null. Also initializes the connection using + * configured initialization SQL (if provided) and sets up a prepared statement pool associated with the + * PoolableManagedConnection if statement pooling is enabled. + */ + @SuppressWarnings("resource") // Connection is released elsewhere. + @Override + public synchronized PooledObject makeObject() throws SQLException { + Connection conn = getConnectionFactory().createConnection(); + if (conn == null) { + throw new IllegalStateException("Connection factory returned null from createConnection"); + } + initializeConnection(conn); + if (getPoolStatements()) { + conn = new PoolingConnection(conn); + final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); + config.setMaxTotalPerKey(-1); + config.setBlockWhenExhausted(false); + config.setMaxWait(Duration.ZERO); + config.setMaxIdlePerKey(1); + config.setMaxTotal(getMaxOpenPreparedStatements()); + final ObjectName dataSourceJmxName = getDataSourceJmxName(); + final long connIndex = getConnectionIndex().getAndIncrement(); + if (dataSourceJmxName != null) { + final StringBuilder base = new StringBuilder(dataSourceJmxName.toString()); + base.append(Constants.JMX_CONNECTION_BASE_EXT); + base.append(connIndex); + config.setJmxNameBase(base.toString()); + config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); + } else { + config.setJmxEnabled(false); + } + final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>( + (PoolingConnection) conn, config); + ((PoolingConnection) conn).setStatementPool(stmtPool); + ((PoolingConnection) conn).setCacheState(getCacheState()); + } + final PoolableManagedConnection pmc = new PoolableManagedConnection(transactionRegistry, conn, getPool(), + getDisconnectionSqlCodes(), getDisconnectionIgnoreSqlCodes(), isFastFailValidation()); + pmc.setCacheState(getCacheState()); + return new DefaultPooledObject<>(pmc); + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java index ad26646fe1..b5676a8616 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java @@ -1,203 +1,203 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.lang.ref.WeakReference; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Objects; - -import javax.transaction.RollbackException; -import javax.transaction.Status; -import javax.transaction.Synchronization; -import javax.transaction.SystemException; -import javax.transaction.Transaction; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAResource; - -/** - * TransactionContext represents the association between a single XAConnectionFactory and a Transaction. This context - * contains a single shared connection which should be used by all ManagedConnections for the XAConnectionFactory, the - * ability to listen for the transaction completion event, and a method to check the status of the transaction. - * - * @since 2.0 - */ -public class TransactionContext { - private final TransactionRegistry transactionRegistry; - private final WeakReference transactionRef; - private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; - private Connection sharedConnection; - private boolean transactionComplete; - - /** - * Provided for backwards compatibility - * - * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the - * shared connection - * @param transaction the transaction - */ - public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction) { - this (transactionRegistry, transaction, null); - } - - /** - * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The TransactionRegistry is - * used to obtain the XAResource for the shared connection when it is enlisted in the transaction. - * - * @param transactionRegistry - * the TransactionRegistry used to obtain the XAResource for the shared connection - * @param transaction - * the transaction - * @param transactionSynchronizationRegistry - * The optional TSR to register synchronizations with - * @since 2.6.0 - */ - public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction, - final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { - Objects.requireNonNull(transactionRegistry, "transactionRegistry"); - Objects.requireNonNull(transaction, "transaction"); - this.transactionRegistry = transactionRegistry; - this.transactionRef = new WeakReference<>(transaction); - this.transactionComplete = false; - this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; - } - - /** - * Adds a listener for transaction completion events. - * - * @param listener - * the listener to add - * @throws SQLException - * if a problem occurs adding the listener to the transaction - */ - public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { - try { - if (!isActive()) { - final Transaction transaction = this.transactionRef.get(); - listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED); - return; - } - final Synchronization s = new SynchronizationAdapter() { - @Override - public void afterCompletion(final int status) { - listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); - } - }; - if (transactionSynchronizationRegistry != null) { - transactionSynchronizationRegistry.registerInterposedSynchronization(s); - } else { - getTransaction().registerSynchronization(s); - } - } catch (final RollbackException ignored) { - // JTA spec doesn't let us register with a transaction marked rollback only - // just ignore this and the tx state will be cleared another way. - } catch (final Exception e) { - throw new SQLException("Unable to register transaction context listener", e); - } - } - - /** - * Sets the transaction complete flag to true. - * - * @since 2.4.0 - */ - public void completeTransaction() { - this.transactionComplete = true; - } - - /** - * Gets the connection shared by all ManagedConnections in the transaction. Specifically, connection using the same - * XAConnectionFactory from which the TransactionRegistry was obtained. - * - * @return the shared connection for this transaction - */ - public Connection getSharedConnection() { - return sharedConnection; - } - - private Transaction getTransaction() throws SQLException { - final Transaction transaction = this.transactionRef.get(); - if (transaction == null) { - throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); - } - return transaction; - } - - /** - * True if the transaction is active or marked for rollback only. - * - * @return true if the transaction is active or marked for rollback only; false otherwise - * @throws SQLException - * if a problem occurs obtaining the transaction status - */ - public boolean isActive() throws SQLException { - try { - final Transaction transaction = this.transactionRef.get(); - if (transaction == null) { - return false; - } - final int status = transaction.getStatus(); - return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; - } catch (final SystemException e) { - throw new SQLException("Unable to get transaction status", e); - } - } - - /** - * Gets the transaction complete flag to true. - * - * @return The transaction complete flag. - * @since 2.4.0 - */ - public boolean isTransactionComplete() { - return this.transactionComplete; - } - - /** - * Sets the shared connection for this transaction. The shared connection is enlisted in the transaction. - * - * @param sharedConnection - * the shared connection - * @throws SQLException - * if a shared connection is already set, if XAResource for the connection could not be found in the - * transaction registry, or if there was a problem enlisting the connection in the transaction - */ - public void setSharedConnection(final Connection sharedConnection) throws SQLException { - if (this.sharedConnection != null) { - throw new IllegalStateException("A shared connection is already set"); - } - - // This is the first use of the connection in this transaction, so we must - // enlist it in the transaction - final Transaction transaction = getTransaction(); - try { - final XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); - if (!transaction.enlistResource(xaResource)) { - throw new SQLException("Unable to enlist connection in transaction: enlistResource returns 'false'."); - } - } catch (final IllegalStateException e) { - // This can happen if the transaction is already timed out - throw new SQLException("Unable to enlist connection in the transaction", e); - } catch (final RollbackException ignored) { - // transaction was rolled back... proceed as if there never was a transaction - } catch (final SystemException e) { - throw new SQLException("Unable to enlist connection the transaction", e); - } - - this.sharedConnection = sharedConnection; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.lang.ref.WeakReference; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAResource; + +/** + * TransactionContext represents the association between a single XAConnectionFactory and a Transaction. This context + * contains a single shared connection which should be used by all ManagedConnections for the XAConnectionFactory, the + * ability to listen for the transaction completion event, and a method to check the status of the transaction. + * + * @since 2.0 + */ +public class TransactionContext { + private final TransactionRegistry transactionRegistry; + private final WeakReference transactionRef; + private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; + private Connection sharedConnection; + private boolean transactionComplete; + + /** + * Provided for backwards compatibility + * + * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the + * shared connection + * @param transaction the transaction + */ + public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction) { + this (transactionRegistry, transaction, null); + } + + /** + * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The TransactionRegistry is + * used to obtain the XAResource for the shared connection when it is enlisted in the transaction. + * + * @param transactionRegistry + * the TransactionRegistry used to obtain the XAResource for the shared connection + * @param transaction + * the transaction + * @param transactionSynchronizationRegistry + * The optional TSR to register synchronizations with + * @since 2.6.0 + */ + public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction, + final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { + Objects.requireNonNull(transactionRegistry, "transactionRegistry"); + Objects.requireNonNull(transaction, "transaction"); + this.transactionRegistry = transactionRegistry; + this.transactionRef = new WeakReference<>(transaction); + this.transactionComplete = false; + this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; + } + + /** + * Adds a listener for transaction completion events. + * + * @param listener + * the listener to add + * @throws SQLException + * if a problem occurs adding the listener to the transaction + */ + public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException { + try { + if (!isActive()) { + final Transaction transaction = this.transactionRef.get(); + listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED); + return; + } + final Synchronization s = new SynchronizationAdapter() { + @Override + public void afterCompletion(final int status) { + listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED); + } + }; + if (transactionSynchronizationRegistry != null) { + transactionSynchronizationRegistry.registerInterposedSynchronization(s); + } else { + getTransaction().registerSynchronization(s); + } + } catch (final RollbackException ignored) { + // JTA spec doesn't let us register with a transaction marked rollback only + // just ignore this and the tx state will be cleared another way. + } catch (final Exception e) { + throw new SQLException("Unable to register transaction context listener", e); + } + } + + /** + * Sets the transaction complete flag to true. + * + * @since 2.4.0 + */ + public void completeTransaction() { + this.transactionComplete = true; + } + + /** + * Gets the connection shared by all ManagedConnections in the transaction. Specifically, connection using the same + * XAConnectionFactory from which the TransactionRegistry was obtained. + * + * @return the shared connection for this transaction + */ + public Connection getSharedConnection() { + return sharedConnection; + } + + private Transaction getTransaction() throws SQLException { + final Transaction transaction = this.transactionRef.get(); + if (transaction == null) { + throw new SQLException("Unable to enlist connection because the transaction has been garbage collected"); + } + return transaction; + } + + /** + * True if the transaction is active or marked for rollback only. + * + * @return true if the transaction is active or marked for rollback only; false otherwise + * @throws SQLException + * if a problem occurs obtaining the transaction status + */ + public boolean isActive() throws SQLException { + try { + final Transaction transaction = this.transactionRef.get(); + if (transaction == null) { + return false; + } + final int status = transaction.getStatus(); + return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK; + } catch (final SystemException e) { + throw new SQLException("Unable to get transaction status", e); + } + } + + /** + * Gets the transaction complete flag to true. + * + * @return The transaction complete flag. + * @since 2.4.0 + */ + public boolean isTransactionComplete() { + return this.transactionComplete; + } + + /** + * Sets the shared connection for this transaction. The shared connection is enlisted in the transaction. + * + * @param sharedConnection + * the shared connection + * @throws SQLException + * if a shared connection is already set, if XAResource for the connection could not be found in the + * transaction registry, or if there was a problem enlisting the connection in the transaction + */ + public void setSharedConnection(final Connection sharedConnection) throws SQLException { + if (this.sharedConnection != null) { + throw new IllegalStateException("A shared connection is already set"); + } + + // This is the first use of the connection in this transaction, so we must + // enlist it in the transaction + final Transaction transaction = getTransaction(); + try { + final XAResource xaResource = transactionRegistry.getXAResource(sharedConnection); + if (!transaction.enlistResource(xaResource)) { + throw new SQLException("Unable to enlist connection in transaction: enlistResource returns 'false'."); + } + } catch (final IllegalStateException e) { + // This can happen if the transaction is already timed out + throw new SQLException("Unable to enlist connection in the transaction", e); + } catch (final RollbackException ignored) { + // transaction was rolled back... proceed as if there never was a transaction + } catch (final SystemException e) { + throw new SQLException("Unable to enlist connection the transaction", e); + } + + this.sharedConnection = sharedConnection; + } +} diff --git a/src/main/java/org/apache/commons/dbcp2/managed/TransactionRegistry.java b/src/main/java/org/apache/commons/dbcp2/managed/TransactionRegistry.java index 203d043564..babbceb012 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/TransactionRegistry.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/TransactionRegistry.java @@ -1,152 +1,152 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Map; -import java.util.Objects; -import java.util.WeakHashMap; - -import javax.transaction.SystemException; -import javax.transaction.Transaction; -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAResource; - -import org.apache.commons.dbcp2.DelegatingConnection; - -/** - * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory. - *

- * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives - * the ManagedConnection a way to enlist connections in a transaction, allowing for the maximal rescue of DBCP. - *

- * - * @since 2.0 - */ -public class TransactionRegistry { - private final TransactionManager transactionManager; - private final Map caches = new WeakHashMap<>(); - private final Map xaResources = new WeakHashMap<>(); - private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; - - /** - * Provided for backwards compatibility - * @param transactionManager the transaction manager used to enlist connections - */ - public TransactionRegistry(final TransactionManager transactionManager) { - this (transactionManager, null); - } - - /** - * Creates a TransactionRegistry for the specified transaction manager. - * - * @param transactionManager - * the transaction manager used to enlist connections. - * @param transactionSynchronizationRegistry - * The optional TSR to register synchronizations with - * @since 2.6.0 - */ - public TransactionRegistry(final TransactionManager transactionManager, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { - this.transactionManager = transactionManager; - this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; - } - - /** - * Gets the active TransactionContext or null if not Transaction is active. - * - * @return The active TransactionContext or null if no Transaction is active. - * @throws SQLException - * Thrown when an error occurs while fetching the transaction. - */ - public TransactionContext getActiveTransactionContext() throws SQLException { - Transaction transaction = null; - try { - transaction = transactionManager.getTransaction(); - - // was there a transaction? - if (transaction == null) { - return null; - } - - // This is the transaction on the thread so no need to check its status - we should try to use it and - // fail later based on the subsequent status - } catch (final SystemException e) { - throw new SQLException("Unable to determine current transaction ", e); - } - - // register the context (or create a new one) - synchronized (this) { - return caches.computeIfAbsent(transaction, k -> new TransactionContext(this, k, transactionSynchronizationRegistry)); - } - } - - private Connection getConnectionKey(final Connection connection) { - final Connection result; - if (connection instanceof DelegatingConnection) { - result = ((DelegatingConnection) connection).getInnermostDelegateInternal(); - } else { - result = connection; - } - return result; - } - - /** - * Gets the XAResource registered for the connection. - * - * @param connection - * the connection - * @return The XAResource registered for the connection; never null. - * @throws SQLException - * Thrown when the connection does not have a registered XAResource. - */ - public synchronized XAResource getXAResource(final Connection connection) throws SQLException { - Objects.requireNonNull(connection, "connection"); - final Connection key = getConnectionKey(connection); - final XAResource xaResource = xaResources.get(key); - if (xaResource == null) { - throw new SQLException("Connection does not have a registered XAResource " + connection); - } - return xaResource; - } - - /** - * Registers the association between a Connection and a XAResource. When a connection is enlisted in a transaction, - * it is actually the XAResource that is given to the transaction manager. - * - * @param connection - * The JDBC connection. - * @param xaResource - * The XAResource which managed the connection within a transaction. - */ - public synchronized void registerConnection(final Connection connection, final XAResource xaResource) { - Objects.requireNonNull(connection, "connection"); - Objects.requireNonNull(xaResource, "xaResource"); - xaResources.put(connection, xaResource); - } - - /** - * Unregisters a destroyed connection from {@link TransactionRegistry}. - * - * @param connection - * A destroyed connection from {@link TransactionRegistry}. - */ - public synchronized void unregisterConnection(final Connection connection) { - xaResources.remove(getConnectionKey(connection)); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; + +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAResource; + +import org.apache.commons.dbcp2.DelegatingConnection; + +/** + * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory. + *

+ * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives + * the ManagedConnection a way to enlist connections in a transaction, allowing for the maximal rescue of DBCP. + *

+ * + * @since 2.0 + */ +public class TransactionRegistry { + private final TransactionManager transactionManager; + private final Map caches = new WeakHashMap<>(); + private final Map xaResources = new WeakHashMap<>(); + private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; + + /** + * Provided for backwards compatibility + * @param transactionManager the transaction manager used to enlist connections + */ + public TransactionRegistry(final TransactionManager transactionManager) { + this (transactionManager, null); + } + + /** + * Creates a TransactionRegistry for the specified transaction manager. + * + * @param transactionManager + * the transaction manager used to enlist connections. + * @param transactionSynchronizationRegistry + * The optional TSR to register synchronizations with + * @since 2.6.0 + */ + public TransactionRegistry(final TransactionManager transactionManager, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { + this.transactionManager = transactionManager; + this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; + } + + /** + * Gets the active TransactionContext or null if not Transaction is active. + * + * @return The active TransactionContext or null if no Transaction is active. + * @throws SQLException + * Thrown when an error occurs while fetching the transaction. + */ + public TransactionContext getActiveTransactionContext() throws SQLException { + Transaction transaction = null; + try { + transaction = transactionManager.getTransaction(); + + // was there a transaction? + if (transaction == null) { + return null; + } + + // This is the transaction on the thread so no need to check its status - we should try to use it and + // fail later based on the subsequent status + } catch (final SystemException e) { + throw new SQLException("Unable to determine current transaction ", e); + } + + // register the context (or create a new one) + synchronized (this) { + return caches.computeIfAbsent(transaction, k -> new TransactionContext(this, k, transactionSynchronizationRegistry)); + } + } + + private Connection getConnectionKey(final Connection connection) { + final Connection result; + if (connection instanceof DelegatingConnection) { + result = ((DelegatingConnection) connection).getInnermostDelegateInternal(); + } else { + result = connection; + } + return result; + } + + /** + * Gets the XAResource registered for the connection. + * + * @param connection + * the connection + * @return The XAResource registered for the connection; never null. + * @throws SQLException + * Thrown when the connection does not have a registered XAResource. + */ + public synchronized XAResource getXAResource(final Connection connection) throws SQLException { + Objects.requireNonNull(connection, "connection"); + final Connection key = getConnectionKey(connection); + final XAResource xaResource = xaResources.get(key); + if (xaResource == null) { + throw new SQLException("Connection does not have a registered XAResource " + connection); + } + return xaResource; + } + + /** + * Registers the association between a Connection and a XAResource. When a connection is enlisted in a transaction, + * it is actually the XAResource that is given to the transaction manager. + * + * @param connection + * The JDBC connection. + * @param xaResource + * The XAResource which managed the connection within a transaction. + */ + public synchronized void registerConnection(final Connection connection, final XAResource xaResource) { + Objects.requireNonNull(connection, "connection"); + Objects.requireNonNull(xaResource, "xaResource"); + xaResources.put(connection, xaResource); + } + + /** + * Unregisters a destroyed connection from {@link TransactionRegistry}. + * + * @param connection + * A destroyed connection from {@link TransactionRegistry}. + */ + public synchronized void unregisterConnection(final Connection connection) { + xaResources.remove(getConnectionKey(connection)); + } +} diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index ad5ca88e85..bbdae5b190 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -1,100 +1,100 @@ - - - - - - Overview - Commons Documentation Team - - - - -
- -

Many Apache projects support interaction with a relational database. -Creating a new connection for each user can be time consuming (often -requiring multiple seconds of clock time), in order to perform a database -transaction that might take milliseconds. Opening a connection per user -can be unfeasible in a publicly-hosted Internet application where the -number of simultaneous users can be very large. Accordingly, developers -often wish to share a "pool" of open connections between all of the -application's current users. The number of users actually performing -a request at any given time is usually a very small percentage of the -total number of active users, and during request processing is the only -time that a database connection is required. The application itself logs -into the DBMS, and handles any user account issues internally.

- -

There are several Database Connection Pools already available, both -within Apache products and elsewhere. This Commons package provides an -opportunity to coordinate the efforts required to create and maintain an -efficient, feature-rich package under the ASF license.

- -

The commons-dbcp2 artifact relies on code in the -commons-pool2 artifact to provide the underlying object pool -mechanisms.

- -

DBCP now comes in four different versions to support different versions of -JDBC. Here is how it works:

-

Developing

-
    -
  • DBCP 2.5.0 and up compiles and runs under Java 8 -(JDBC 4.2) and up.
  • -
  • DBCP 2.4.0 compiles and runs under Java 7 -(JDBC 4.1) and above.
  • -
-

Running

-
    -
  • DBCP 2.5.0 and up binaries should be used by applications running on Java 8 and up.
  • -
  • DBCP 2.4.0 binaries should be used by applications running under Java 7.
  • -
-

DBCP 2 is based on -Apache Commons Pool -and provides increased performance, JMX -support as well as numerous other new features compared to DBCP 1.x. Users -upgrading to 2.x should be aware that the Java package name has changed, as well -as the Maven co-ordinates, since DBCP 2.x is not binary compatible with DBCP -1.x. Users should also be aware that some configuration options (e.g. maxActive -to maxTotal) have been renamed to align them with the new names used by Commons -Pool.

- -
- -
-

- See the downloads page for information on - obtaining releases. -

-
- -
- -

The -Javadoc API documents -are available online. In particular, you should -read the package overview of the -org.apache.commons.dbcp2 -package for an overview of how to use DBCP.

- -

There are -several examples -of using DBCP available.

- -
- - -
+ + + + + + Overview + Commons Documentation Team + + + + +
+ +

Many Apache projects support interaction with a relational database. +Creating a new connection for each user can be time consuming (often +requiring multiple seconds of clock time), in order to perform a database +transaction that might take milliseconds. Opening a connection per user +can be unfeasible in a publicly-hosted Internet application where the +number of simultaneous users can be very large. Accordingly, developers +often wish to share a "pool" of open connections between all of the +application's current users. The number of users actually performing +a request at any given time is usually a very small percentage of the +total number of active users, and during request processing is the only +time that a database connection is required. The application itself logs +into the DBMS, and handles any user account issues internally.

+ +

There are several Database Connection Pools already available, both +within Apache products and elsewhere. This Commons package provides an +opportunity to coordinate the efforts required to create and maintain an +efficient, feature-rich package under the ASF license.

+ +

The commons-dbcp2 artifact relies on code in the +commons-pool2 artifact to provide the underlying object pool +mechanisms.

+ +

DBCP now comes in four different versions to support different versions of +JDBC. Here is how it works:

+

Developing

+
    +
  • DBCP 2.5.0 and up compiles and runs under Java 8 +(JDBC 4.2) and up.
  • +
  • DBCP 2.4.0 compiles and runs under Java 7 +(JDBC 4.1) and above.
  • +
+

Running

+
    +
  • DBCP 2.5.0 and up binaries should be used by applications running on Java 8 and up.
  • +
  • DBCP 2.4.0 binaries should be used by applications running under Java 7.
  • +
+

DBCP 2 is based on +Apache Commons Pool +and provides increased performance, JMX +support as well as numerous other new features compared to DBCP 1.x. Users +upgrading to 2.x should be aware that the Java package name has changed, as well +as the Maven co-ordinates, since DBCP 2.x is not binary compatible with DBCP +1.x. Users should also be aware that some configuration options (e.g. maxActive +to maxTotal) have been renamed to align them with the new names used by Commons +Pool.

+ +
+ +
+

+ See the downloads page for information on + obtaining releases. +

+
+ +
+ +

The +Javadoc API documents +are available online. In particular, you should +read the package overview of the +org.apache.commons.dbcp2 +package for an overview of how to use DBCP.

+ +

There are +several examples +of using DBCP available.

+ +
+ + +
diff --git a/src/test/java/org/apache/commons/dbcp2/StackMessageLog.java b/src/test/java/org/apache/commons/dbcp2/StackMessageLog.java index f2b214d09d..be3849f715 100644 --- a/src/test/java/org/apache/commons/dbcp2/StackMessageLog.java +++ b/src/test/java/org/apache/commons/dbcp2/StackMessageLog.java @@ -1,127 +1,127 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.EmptyStackException; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.impl.SimpleLog; - -/** - * A logger that pushes log messages onto a stack. The stack itself is static. - * To get a log exclusive to a test case, use explicit lock / unlock and clear. - */ -public class StackMessageLog extends SimpleLog { - - private static final long serialVersionUID = 1L; - private static final Stack MESSAGE_STACK = new Stack<>(); - private static final Lock LOCK = new ReentrantLock(); - - public static void clear() { - LOCK.lock(); - try { - MESSAGE_STACK.clear(); - } finally { - LOCK.unlock(); - } - } - - /** - * Gets a copy of the message stack. - *

- * Note: lock the stack first. - *

- * - * @return a new list. - */ - public static List getAll() { - return new ArrayList<>(MESSAGE_STACK); - } - - public static boolean isEmpty() { - return MESSAGE_STACK.isEmpty(); - } - - /** - * Obtains an exclusive lock on the log. - */ - public static void lock() { - LOCK.lock(); - } - - /** - * @return the most recent log message, or null if the log is empty - */ - public static String popMessage() { - LOCK.lock(); - try { - return MESSAGE_STACK.pop(); - } catch (final EmptyStackException ex) { - // ignore, return null - return null; - } finally { - LOCK.unlock(); - } - } - - /** - * Relinquishes exclusive lock on the log. - */ - public static void unLock() { - try { - LOCK.unlock(); - } catch (final IllegalMonitorStateException ex) { - // ignore - } - } - - public StackMessageLog(final String name) { - super(name); - } - - /** - * Ignores type. Pushes message followed by stack trace of t onto the stack. - */ - @Override - protected void log(final int type, final Object message, final Throwable t) { - LOCK.lock(); - try { - final StringBuilder buf = new StringBuilder(); - buf.append(message.toString()); - if (t != null) { - buf.append(" <"); - buf.append(t.toString()); - buf.append(">"); - final java.io.StringWriter sw = new StringWriter(1024); - try (final java.io.PrintWriter pw = new PrintWriter(sw)) { - t.printStackTrace(pw); - } - buf.append(sw.toString()); - } - MESSAGE_STACK.push(buf.toString()); - } finally { - LOCK.unlock(); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.EmptyStackException; +import java.util.List; +import java.util.Stack; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.impl.SimpleLog; + +/** + * A logger that pushes log messages onto a stack. The stack itself is static. + * To get a log exclusive to a test case, use explicit lock / unlock and clear. + */ +public class StackMessageLog extends SimpleLog { + + private static final long serialVersionUID = 1L; + private static final Stack MESSAGE_STACK = new Stack<>(); + private static final Lock LOCK = new ReentrantLock(); + + public static void clear() { + LOCK.lock(); + try { + MESSAGE_STACK.clear(); + } finally { + LOCK.unlock(); + } + } + + /** + * Gets a copy of the message stack. + *

+ * Note: lock the stack first. + *

+ * + * @return a new list. + */ + public static List getAll() { + return new ArrayList<>(MESSAGE_STACK); + } + + public static boolean isEmpty() { + return MESSAGE_STACK.isEmpty(); + } + + /** + * Obtains an exclusive lock on the log. + */ + public static void lock() { + LOCK.lock(); + } + + /** + * @return the most recent log message, or null if the log is empty + */ + public static String popMessage() { + LOCK.lock(); + try { + return MESSAGE_STACK.pop(); + } catch (final EmptyStackException ex) { + // ignore, return null + return null; + } finally { + LOCK.unlock(); + } + } + + /** + * Relinquishes exclusive lock on the log. + */ + public static void unLock() { + try { + LOCK.unlock(); + } catch (final IllegalMonitorStateException ex) { + // ignore + } + } + + public StackMessageLog(final String name) { + super(name); + } + + /** + * Ignores type. Pushes message followed by stack trace of t onto the stack. + */ + @Override + protected void log(final int type, final Object message, final Throwable t) { + LOCK.lock(); + try { + final StringBuilder buf = new StringBuilder(); + buf.append(message.toString()); + if (t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + final java.io.StringWriter sw = new StringWriter(1024); + try (final java.io.PrintWriter pw = new PrintWriter(sw)) { + t.printStackTrace(pw); + } + buf.append(sw.toString()); + } + MESSAGE_STACK.push(buf.toString()); + } finally { + LOCK.unlock(); + } + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/TestAbandonedBasicDataSource.java b/src/test/java/org/apache/commons/dbcp2/TestAbandonedBasicDataSource.java index da1d0c2bac..2e21b649f7 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestAbandonedBasicDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/TestAbandonedBasicDataSource.java @@ -1,420 +1,420 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.time.Duration; -import java.time.Instant; - -import org.apache.commons.pool2.KeyedObjectPool; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * TestSuite for BasicDataSource with abandoned connection trace enabled - */ -public class TestAbandonedBasicDataSource extends TestBasicDataSource { - - private StringWriter sw; - - /** - * Verifies that con.lastUsed has been updated and then resets it to 0 - */ - private void assertAndReset(final DelegatingConnection con) { - assertTrue(con.getLastUsedInstant().compareTo(Instant.EPOCH) > 0); - con.setLastUsed(Instant.EPOCH); - } - - /** - * Verifies that PreparedStatement executeXxx methods update lastUsed on the parent connection - */ - private void checkLastUsedPreparedStatement(final PreparedStatement ps, final DelegatingConnection conn) throws Exception { - ps.execute(); - assertAndReset(conn); - try (ResultSet rs = ps.executeQuery()) { - Assertions.assertNotNull(rs); - } - assertAndReset(conn); - ps.executeUpdate(); - assertAndReset(conn); - } - - /** - * Verifies that Statement executeXxx methods update lastUsed on the parent connection - */ - private void checkLastUsedStatement(final Statement st, final DelegatingConnection conn) throws Exception { - st.execute(""); - assertAndReset(conn); - st.execute("", new int[] {}); - assertAndReset(conn); - st.execute("", 0); - assertAndReset(conn); - st.executeBatch(); - assertAndReset(conn); - st.executeLargeBatch(); - assertAndReset(conn); - try (ResultSet rs = st.executeQuery("")) { - Assertions.assertNotNull(rs); - } - assertAndReset(conn); - st.executeUpdate(""); - assertAndReset(conn); - st.executeUpdate("", new int[] {}); - assertAndReset(conn); - st.executeLargeUpdate("", new int[] {}); - assertAndReset(conn); - st.executeUpdate("", 0); - assertAndReset(conn); - st.executeLargeUpdate("", 0); - assertAndReset(conn); - st.executeUpdate("", new String[] {}); - assertAndReset(conn); - st.executeLargeUpdate("", new String[] {}); - assertAndReset(conn); - } - - private void createStatement(final Connection conn) throws Exception { - final PreparedStatement ps = conn.prepareStatement(""); - Assertions.assertNotNull(ps); - } - - @Override - @BeforeEach - public void setUp() throws Exception { - super.setUp(); - // abandoned enabled but should not affect the basic tests - // (very high timeout) - ds.setLogAbandoned(true); - ds.setRemoveAbandonedOnBorrow(true); - ds.setRemoveAbandonedOnMaintenance(true); - ds.setRemoveAbandonedTimeout(Duration.ofSeconds(10)); - sw = new StringWriter(); - ds.setAbandonedLogWriter(new PrintWriter(sw)); - } - - @Test - public void testAbandoned() throws Exception { - // force abandoned - ds.setRemoveAbandonedTimeout(Duration.ZERO); - ds.setMaxTotal(1); - - for (int i = 0; i < 3; i++) { - assertNotNull(ds.getConnection()); - } - } - - @Test - public void testAbandonedClose() throws Exception { - // force abandoned - ds.setRemoveAbandonedTimeout(Duration.ZERO); - ds.setMaxTotal(1); - ds.setAccessToUnderlyingConnectionAllowed(true); - - try (Connection conn1 = getConnection()) { - assertNotNull(conn1); - assertEquals(1, ds.getNumActive()); - - try (Connection conn2 = getConnection()) { - // Attempt to borrow object triggers abandoned cleanup - // conn1 should be closed by the pool to make room - assertNotNull(conn2); - assertEquals(1, ds.getNumActive()); - // Verify that conn1 is closed - assertTrue(((DelegatingConnection) conn1).getInnermostDelegate().isClosed()); - // Verify that conn1 is aborted - final TesterConnection tCon = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); - assertTrue(tCon.isAborted()); - - } - assertEquals(0, ds.getNumActive()); - - // Second close on conn1 is OK as of dbcp 1.3 - } - assertEquals(0, ds.getNumActive()); - final String string = sw.toString(); - assertTrue(string.contains("testAbandonedClose"), string); - } - - @Test - public void testAbandonedCloseWithExceptions() throws Exception { - // force abandoned - ds.setRemoveAbandonedTimeout(Duration.ZERO); - ds.setMaxTotal(1); - ds.setAccessToUnderlyingConnectionAllowed(true); - - final Connection conn1 = getConnection(); - assertNotNull(conn1); - assertEquals(1, ds.getNumActive()); - - final Connection conn2 = getConnection(); - assertNotNull(conn2); - assertEquals(1, ds.getNumActive()); - - // set an IO failure causing the isClosed method to fail - final TesterConnection tconn1 = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); - tconn1.setFailure(new IOException("network error")); - final TesterConnection tconn2 = (TesterConnection) ((DelegatingConnection) conn2).getInnermostDelegate(); - tconn2.setFailure(new IOException("network error")); - - try { - conn2.close(); - } catch (final SQLException ex) { - /* Ignore */ - } - assertEquals(0, ds.getNumActive()); - - try { - conn1.close(); - } catch (final SQLException ex) { - // ignore - } - assertEquals(0, ds.getNumActive()); - final String string = sw.toString(); - assertTrue(string.contains("testAbandonedCloseWithExceptions"), string); - } - - @Test - public void testAbandonedStackTraces() throws Exception { - // force abandoned - ds.setRemoveAbandonedTimeout(Duration.ZERO); - ds.setMaxTotal(1); - ds.setAccessToUnderlyingConnectionAllowed(true); - ds.setAbandonedUsageTracking(true); - - try (Connection conn1 = getConnection()) { - assertNotNull(conn1); - assertEquals(1, ds.getNumActive()); - // Use the connection - try (Statement stmt = conn1.createStatement()) { - assertNotNull(stmt); - stmt.execute("SELECT 1 FROM DUAL"); - } - - try (Connection conn2 = getConnection()) { - // Attempt to borrow object triggers abandoned cleanup - // conn1 should be closed by the pool to make room - assertNotNull(conn2); - assertEquals(1, ds.getNumActive()); - // Verify that conn1 is closed - assertTrue(((DelegatingConnection) conn1).getInnermostDelegate().isClosed()); - // Verify that conn1 is aborted - final TesterConnection tCon = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); - assertTrue(tCon.isAborted()); - - } - assertEquals(0, ds.getNumActive()); - } - assertEquals(0, ds.getNumActive()); - final String stackTrace = sw.toString(); - assertTrue(stackTrace.contains("testAbandonedStackTraces"), stackTrace); - assertTrue(stackTrace.contains("Pooled object created"), stackTrace); - assertTrue(stackTrace.contains("The last code to use this object was:"), stackTrace); - } - - /** - * DBCP-180 - verify that a GC can clean up an unused Statement when it is no longer referenced even when it is tracked via the AbandonedTrace mechanism. - */ - @Test - public void testGarbageCollectorCleanUp01() throws Exception { - try (DelegatingConnection conn = (DelegatingConnection) ds.getConnection()) { - Assertions.assertEquals(0, conn.getTrace().size()); - createStatement(conn); - Assertions.assertEquals(1, conn.getTrace().size()); - System.gc(); - Assertions.assertEquals(0, conn.getTrace().size()); - } - } - - /** - * DBCP-180 - things get more interesting with statement pooling. - */ - @Test - public void testGarbageCollectorCleanUp02() throws Exception { - ds.setPoolPreparedStatements(true); - ds.setAccessToUnderlyingConnectionAllowed(true); - final DelegatingConnection conn = (DelegatingConnection) ds.getConnection(); - final PoolableConnection poolableConn = (PoolableConnection) conn.getDelegate(); - final PoolingConnection poolingConn = (PoolingConnection) poolableConn.getDelegate(); - final KeyedObjectPool gkop = poolingConn.getStatementPool(); - Assertions.assertEquals(0, conn.getTrace().size()); - Assertions.assertEquals(0, gkop.getNumActive()); - createStatement(conn); - Assertions.assertEquals(1, conn.getTrace().size()); - Assertions.assertEquals(1, gkop.getNumActive()); - System.gc(); - // Finalization happens in a separate thread. Give the test time for - // that to complete. - int count = 0; - while (count < 50 && gkop.getNumActive() > 0) { - Thread.sleep(100); - count++; - } - Assertions.assertEquals(0, gkop.getNumActive()); - Assertions.assertEquals(0, conn.getTrace().size()); - } - - /** - * Verify that lastUsed property is updated when a connection creates or prepares a statement - */ - @Test - public void testLastUsed() throws Exception { - ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); - ds.setMaxTotal(2); - try (Connection conn1 = ds.getConnection()) { - Thread.sleep(500); - try (Statement s = conn1.createStatement()) { - // Should reset lastUsed - } - Thread.sleep(800); - final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup - try (Statement s = conn1.createStatement()) { - // Should still be OK - } - conn2.close(); - Thread.sleep(500); - try (PreparedStatement ps = conn1.prepareStatement("SELECT 1 FROM DUAL")) { - // reset - } - Thread.sleep(800); - try (Connection c = ds.getConnection()) { - // trigger abandoned cleanup again - } - try (Statement s = conn1.createStatement()) { - // empty - } - } - } - - /** - * DBCP-343 - verify that using a DelegatingStatement updates the lastUsed on the parent connection - */ - @Test - public void testLastUsedLargePreparedStatementUse() throws Exception { - ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); - ds.setMaxTotal(2); - try (Connection conn1 = ds.getConnection(); Statement st = conn1.createStatement()) { - final String querySQL = "SELECT 1 FROM DUAL"; - Thread.sleep(500); - try (ResultSet rs = st.executeQuery(querySQL)) { - Assertions.assertNotNull(rs); // Should reset lastUsed - } - Thread.sleep(800); - try (final Connection conn2 = ds.getConnection()) { // triggers abandoned cleanup - try (ResultSet rs = st.executeQuery(querySQL)) { - Assertions.assertNotNull(rs); // Should still be OK - } - } - Thread.sleep(500); - st.executeLargeUpdate(""); // Should also reset - Thread.sleep(800); - try (Connection c = ds.getConnection()) { - // trigger abandoned cleanup again - } - try (Statement s = conn1.createStatement()) { - // Connection should still be good - } - } - } - - /** - * Verify that lastUsed property is updated when a connection prepares a callable statement. - */ - @Test - public void testLastUsedPrepareCall() throws Exception { - ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); - ds.setMaxTotal(2); - try (Connection conn1 = ds.getConnection()) { - Thread.sleep(500); - try (CallableStatement cs = conn1.prepareCall("{call home}")) { - // Should reset lastUsed - } - Thread.sleep(800); - final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup - try (CallableStatement cs = conn1.prepareCall("{call home}")) { - // Should still be OK - } - conn2.close(); - Thread.sleep(500); - try (CallableStatement cs = conn1.prepareCall("{call home}")) { - // reset - } - Thread.sleep(800); - try (Connection c = ds.getConnection()) { - // empty - } - try (Statement s = conn1.createStatement()) { - // trigger abandoned cleanup again - } - } - } - - /** - * DBCP-343 - verify that using a DelegatingStatement updates the lastUsed on the parent connection - */ - @Test - public void testLastUsedPreparedStatementUse() throws Exception { - ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); - ds.setMaxTotal(2); - try (Connection conn1 = ds.getConnection(); Statement st = conn1.createStatement()) { - final String querySQL = "SELECT 1 FROM DUAL"; - Thread.sleep(500); - Assertions.assertNotNull(st.executeQuery(querySQL)); // Should reset lastUsed - Thread.sleep(800); - final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup - Assertions.assertNotNull(st.executeQuery(querySQL)); // Should still be OK - conn2.close(); - Thread.sleep(500); - st.executeUpdate(""); // Should also reset - Thread.sleep(800); - try (Connection c = ds.getConnection()) { - } // trigger abandoned cleanup again - try (Statement s = conn1.createStatement()) { - } // Connection should still be good - } - } - - /** - * DBCP-343 - verify additional operations reset lastUsed on the parent connection - */ - @Test - public void testLastUsedUpdate() throws Exception { - try (DelegatingConnection conn = (DelegatingConnection) ds.getConnection(); - final PreparedStatement ps = conn.prepareStatement(""); - final CallableStatement cs = conn.prepareCall(""); - final Statement st = conn.prepareStatement("")) { - checkLastUsedStatement(ps, conn); - checkLastUsedPreparedStatement(ps, conn); - checkLastUsedStatement(cs, conn); - checkLastUsedPreparedStatement(cs, conn); - checkLastUsedStatement(st, conn); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Duration; +import java.time.Instant; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * TestSuite for BasicDataSource with abandoned connection trace enabled + */ +public class TestAbandonedBasicDataSource extends TestBasicDataSource { + + private StringWriter sw; + + /** + * Verifies that con.lastUsed has been updated and then resets it to 0 + */ + private void assertAndReset(final DelegatingConnection con) { + assertTrue(con.getLastUsedInstant().compareTo(Instant.EPOCH) > 0); + con.setLastUsed(Instant.EPOCH); + } + + /** + * Verifies that PreparedStatement executeXxx methods update lastUsed on the parent connection + */ + private void checkLastUsedPreparedStatement(final PreparedStatement ps, final DelegatingConnection conn) throws Exception { + ps.execute(); + assertAndReset(conn); + try (ResultSet rs = ps.executeQuery()) { + Assertions.assertNotNull(rs); + } + assertAndReset(conn); + ps.executeUpdate(); + assertAndReset(conn); + } + + /** + * Verifies that Statement executeXxx methods update lastUsed on the parent connection + */ + private void checkLastUsedStatement(final Statement st, final DelegatingConnection conn) throws Exception { + st.execute(""); + assertAndReset(conn); + st.execute("", new int[] {}); + assertAndReset(conn); + st.execute("", 0); + assertAndReset(conn); + st.executeBatch(); + assertAndReset(conn); + st.executeLargeBatch(); + assertAndReset(conn); + try (ResultSet rs = st.executeQuery("")) { + Assertions.assertNotNull(rs); + } + assertAndReset(conn); + st.executeUpdate(""); + assertAndReset(conn); + st.executeUpdate("", new int[] {}); + assertAndReset(conn); + st.executeLargeUpdate("", new int[] {}); + assertAndReset(conn); + st.executeUpdate("", 0); + assertAndReset(conn); + st.executeLargeUpdate("", 0); + assertAndReset(conn); + st.executeUpdate("", new String[] {}); + assertAndReset(conn); + st.executeLargeUpdate("", new String[] {}); + assertAndReset(conn); + } + + private void createStatement(final Connection conn) throws Exception { + final PreparedStatement ps = conn.prepareStatement(""); + Assertions.assertNotNull(ps); + } + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + // abandoned enabled but should not affect the basic tests + // (very high timeout) + ds.setLogAbandoned(true); + ds.setRemoveAbandonedOnBorrow(true); + ds.setRemoveAbandonedOnMaintenance(true); + ds.setRemoveAbandonedTimeout(Duration.ofSeconds(10)); + sw = new StringWriter(); + ds.setAbandonedLogWriter(new PrintWriter(sw)); + } + + @Test + public void testAbandoned() throws Exception { + // force abandoned + ds.setRemoveAbandonedTimeout(Duration.ZERO); + ds.setMaxTotal(1); + + for (int i = 0; i < 3; i++) { + assertNotNull(ds.getConnection()); + } + } + + @Test + public void testAbandonedClose() throws Exception { + // force abandoned + ds.setRemoveAbandonedTimeout(Duration.ZERO); + ds.setMaxTotal(1); + ds.setAccessToUnderlyingConnectionAllowed(true); + + try (Connection conn1 = getConnection()) { + assertNotNull(conn1); + assertEquals(1, ds.getNumActive()); + + try (Connection conn2 = getConnection()) { + // Attempt to borrow object triggers abandoned cleanup + // conn1 should be closed by the pool to make room + assertNotNull(conn2); + assertEquals(1, ds.getNumActive()); + // Verify that conn1 is closed + assertTrue(((DelegatingConnection) conn1).getInnermostDelegate().isClosed()); + // Verify that conn1 is aborted + final TesterConnection tCon = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); + assertTrue(tCon.isAborted()); + + } + assertEquals(0, ds.getNumActive()); + + // Second close on conn1 is OK as of dbcp 1.3 + } + assertEquals(0, ds.getNumActive()); + final String string = sw.toString(); + assertTrue(string.contains("testAbandonedClose"), string); + } + + @Test + public void testAbandonedCloseWithExceptions() throws Exception { + // force abandoned + ds.setRemoveAbandonedTimeout(Duration.ZERO); + ds.setMaxTotal(1); + ds.setAccessToUnderlyingConnectionAllowed(true); + + final Connection conn1 = getConnection(); + assertNotNull(conn1); + assertEquals(1, ds.getNumActive()); + + final Connection conn2 = getConnection(); + assertNotNull(conn2); + assertEquals(1, ds.getNumActive()); + + // set an IO failure causing the isClosed method to fail + final TesterConnection tconn1 = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); + tconn1.setFailure(new IOException("network error")); + final TesterConnection tconn2 = (TesterConnection) ((DelegatingConnection) conn2).getInnermostDelegate(); + tconn2.setFailure(new IOException("network error")); + + try { + conn2.close(); + } catch (final SQLException ex) { + /* Ignore */ + } + assertEquals(0, ds.getNumActive()); + + try { + conn1.close(); + } catch (final SQLException ex) { + // ignore + } + assertEquals(0, ds.getNumActive()); + final String string = sw.toString(); + assertTrue(string.contains("testAbandonedCloseWithExceptions"), string); + } + + @Test + public void testAbandonedStackTraces() throws Exception { + // force abandoned + ds.setRemoveAbandonedTimeout(Duration.ZERO); + ds.setMaxTotal(1); + ds.setAccessToUnderlyingConnectionAllowed(true); + ds.setAbandonedUsageTracking(true); + + try (Connection conn1 = getConnection()) { + assertNotNull(conn1); + assertEquals(1, ds.getNumActive()); + // Use the connection + try (Statement stmt = conn1.createStatement()) { + assertNotNull(stmt); + stmt.execute("SELECT 1 FROM DUAL"); + } + + try (Connection conn2 = getConnection()) { + // Attempt to borrow object triggers abandoned cleanup + // conn1 should be closed by the pool to make room + assertNotNull(conn2); + assertEquals(1, ds.getNumActive()); + // Verify that conn1 is closed + assertTrue(((DelegatingConnection) conn1).getInnermostDelegate().isClosed()); + // Verify that conn1 is aborted + final TesterConnection tCon = (TesterConnection) ((DelegatingConnection) conn1).getInnermostDelegate(); + assertTrue(tCon.isAborted()); + + } + assertEquals(0, ds.getNumActive()); + } + assertEquals(0, ds.getNumActive()); + final String stackTrace = sw.toString(); + assertTrue(stackTrace.contains("testAbandonedStackTraces"), stackTrace); + assertTrue(stackTrace.contains("Pooled object created"), stackTrace); + assertTrue(stackTrace.contains("The last code to use this object was:"), stackTrace); + } + + /** + * DBCP-180 - verify that a GC can clean up an unused Statement when it is no longer referenced even when it is tracked via the AbandonedTrace mechanism. + */ + @Test + public void testGarbageCollectorCleanUp01() throws Exception { + try (DelegatingConnection conn = (DelegatingConnection) ds.getConnection()) { + Assertions.assertEquals(0, conn.getTrace().size()); + createStatement(conn); + Assertions.assertEquals(1, conn.getTrace().size()); + System.gc(); + Assertions.assertEquals(0, conn.getTrace().size()); + } + } + + /** + * DBCP-180 - things get more interesting with statement pooling. + */ + @Test + public void testGarbageCollectorCleanUp02() throws Exception { + ds.setPoolPreparedStatements(true); + ds.setAccessToUnderlyingConnectionAllowed(true); + final DelegatingConnection conn = (DelegatingConnection) ds.getConnection(); + final PoolableConnection poolableConn = (PoolableConnection) conn.getDelegate(); + final PoolingConnection poolingConn = (PoolingConnection) poolableConn.getDelegate(); + final KeyedObjectPool gkop = poolingConn.getStatementPool(); + Assertions.assertEquals(0, conn.getTrace().size()); + Assertions.assertEquals(0, gkop.getNumActive()); + createStatement(conn); + Assertions.assertEquals(1, conn.getTrace().size()); + Assertions.assertEquals(1, gkop.getNumActive()); + System.gc(); + // Finalization happens in a separate thread. Give the test time for + // that to complete. + int count = 0; + while (count < 50 && gkop.getNumActive() > 0) { + Thread.sleep(100); + count++; + } + Assertions.assertEquals(0, gkop.getNumActive()); + Assertions.assertEquals(0, conn.getTrace().size()); + } + + /** + * Verify that lastUsed property is updated when a connection creates or prepares a statement + */ + @Test + public void testLastUsed() throws Exception { + ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); + ds.setMaxTotal(2); + try (Connection conn1 = ds.getConnection()) { + Thread.sleep(500); + try (Statement s = conn1.createStatement()) { + // Should reset lastUsed + } + Thread.sleep(800); + final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup + try (Statement s = conn1.createStatement()) { + // Should still be OK + } + conn2.close(); + Thread.sleep(500); + try (PreparedStatement ps = conn1.prepareStatement("SELECT 1 FROM DUAL")) { + // reset + } + Thread.sleep(800); + try (Connection c = ds.getConnection()) { + // trigger abandoned cleanup again + } + try (Statement s = conn1.createStatement()) { + // empty + } + } + } + + /** + * DBCP-343 - verify that using a DelegatingStatement updates the lastUsed on the parent connection + */ + @Test + public void testLastUsedLargePreparedStatementUse() throws Exception { + ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); + ds.setMaxTotal(2); + try (Connection conn1 = ds.getConnection(); Statement st = conn1.createStatement()) { + final String querySQL = "SELECT 1 FROM DUAL"; + Thread.sleep(500); + try (ResultSet rs = st.executeQuery(querySQL)) { + Assertions.assertNotNull(rs); // Should reset lastUsed + } + Thread.sleep(800); + try (final Connection conn2 = ds.getConnection()) { // triggers abandoned cleanup + try (ResultSet rs = st.executeQuery(querySQL)) { + Assertions.assertNotNull(rs); // Should still be OK + } + } + Thread.sleep(500); + st.executeLargeUpdate(""); // Should also reset + Thread.sleep(800); + try (Connection c = ds.getConnection()) { + // trigger abandoned cleanup again + } + try (Statement s = conn1.createStatement()) { + // Connection should still be good + } + } + } + + /** + * Verify that lastUsed property is updated when a connection prepares a callable statement. + */ + @Test + public void testLastUsedPrepareCall() throws Exception { + ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); + ds.setMaxTotal(2); + try (Connection conn1 = ds.getConnection()) { + Thread.sleep(500); + try (CallableStatement cs = conn1.prepareCall("{call home}")) { + // Should reset lastUsed + } + Thread.sleep(800); + final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup + try (CallableStatement cs = conn1.prepareCall("{call home}")) { + // Should still be OK + } + conn2.close(); + Thread.sleep(500); + try (CallableStatement cs = conn1.prepareCall("{call home}")) { + // reset + } + Thread.sleep(800); + try (Connection c = ds.getConnection()) { + // empty + } + try (Statement s = conn1.createStatement()) { + // trigger abandoned cleanup again + } + } + } + + /** + * DBCP-343 - verify that using a DelegatingStatement updates the lastUsed on the parent connection + */ + @Test + public void testLastUsedPreparedStatementUse() throws Exception { + ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1)); + ds.setMaxTotal(2); + try (Connection conn1 = ds.getConnection(); Statement st = conn1.createStatement()) { + final String querySQL = "SELECT 1 FROM DUAL"; + Thread.sleep(500); + Assertions.assertNotNull(st.executeQuery(querySQL)); // Should reset lastUsed + Thread.sleep(800); + final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup + Assertions.assertNotNull(st.executeQuery(querySQL)); // Should still be OK + conn2.close(); + Thread.sleep(500); + st.executeUpdate(""); // Should also reset + Thread.sleep(800); + try (Connection c = ds.getConnection()) { + } // trigger abandoned cleanup again + try (Statement s = conn1.createStatement()) { + } // Connection should still be good + } + } + + /** + * DBCP-343 - verify additional operations reset lastUsed on the parent connection + */ + @Test + public void testLastUsedUpdate() throws Exception { + try (DelegatingConnection conn = (DelegatingConnection) ds.getConnection(); + final PreparedStatement ps = conn.prepareStatement(""); + final CallableStatement cs = conn.prepareCall(""); + final Statement st = conn.prepareStatement("")) { + checkLastUsedStatement(ps, conn); + checkLastUsedPreparedStatement(ps, conn); + checkLastUsedStatement(cs, conn); + checkLastUsedPreparedStatement(cs, conn); + checkLastUsedStatement(st, conn); + } + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/TestDriverManagerConnectionFactory.java b/src/test/java/org/apache/commons/dbcp2/TestDriverManagerConnectionFactory.java index c1499b7ab1..b9ecb588b1 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestDriverManagerConnectionFactory.java +++ b/src/test/java/org/apache/commons/dbcp2/TestDriverManagerConnectionFactory.java @@ -1,157 +1,157 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Properties; - -import javax.sql.DataSource; - -import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.junit.jupiter.api.Test; - -/** - * This test *must* execute before all other tests to be effective as it tests the initialization of DriverManager. Based on the test case for DBCP-212 written - * by Marcos Sanz - */ -public class TestDriverManagerConnectionFactory extends AbstractDriverTest { - - private static final class ConnectionThread implements Runnable { - private final DataSource ds; - private volatile boolean result = true; - - private ConnectionThread(final DataSource ds) { - this.ds = ds; - } - - public boolean getResult() { - return result; - } - - @Override - public void run() { - Connection conn = null; - try { - conn = ds.getConnection(); - } catch (final Exception e) { - e.printStackTrace(); - result = false; - } finally { - if (conn != null) { - try { - conn.close(); - } catch (final Exception e) { - e.printStackTrace(); - result = false; - } - } - } - } - - @Override - public String toString() { - return "ConnectionThread [ds=" + ds + ", result=" + result + "]"; - } - } - - @Test - public void testDriverManagerCredentialsInUrl() throws SQLException { - final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver;user=foo;password=bar", null, - (char[]) null); - cf.createConnection(); - } - - public void testDriverManagerInit(final boolean withProperties) throws Exception { - final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); - config.setMaxTotal(10); - config.setMaxIdle(0); - final Properties properties = new Properties(); - // The names "user" and "password" are specified in - // java.sql.DriverManager.getConnection(String, String, String) - properties.setProperty(Constants.KEY_USER, "foo"); - properties.setProperty(Constants.KEY_PASSWORD, "bar"); - final ConnectionFactory connectionFactory = withProperties ? new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", properties) - : new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", "foo", "bar"); - final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); - poolableConnectionFactory.setDefaultReadOnly(Boolean.FALSE); - poolableConnectionFactory.setDefaultAutoCommit(Boolean.TRUE); - - final GenericObjectPool connectionPool = new GenericObjectPool<>(poolableConnectionFactory, config); - poolableConnectionFactory.setPool(connectionPool); - final PoolingDataSource dataSource = new PoolingDataSource<>(connectionPool); - - final ConnectionThread[] connectionThreads = new ConnectionThread[10]; - final Thread[] threads = new Thread[10]; - - for (int i = 0; i < 10; i++) { - connectionThreads[i] = new ConnectionThread(dataSource); - threads[i] = new Thread(connectionThreads[i]); - } - for (int i = 0; i < 10; i++) { - threads[i].start(); - } - for (int i = 0; i < 10; i++) { - while (threads[i].isAlive()) { // JDK1.5: getState() != Thread.State.TERMINATED) { - Thread.sleep(100); - } - if (!connectionThreads[i].getResult()) { - fail("Exception during getConnection(): " + connectionThreads[i]); - } - } - } - - @Test - public void testDriverManagerInitWithCredentials() throws Exception { - testDriverManagerInit(false); - } - - @Test - public void testDriverManagerInitWithEmptyProperties() throws Exception { - final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver;user=foo;password=bar"); - connectionFactory.createConnection(); - } - - @Test - public void testDriverManagerInitWithProperties() throws Exception { - testDriverManagerInit(true); - } - - @Test - public void testDriverManagerWithoutCredentials() { - final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", null, (char[]) null); - assertThrows(ArrayIndexOutOfBoundsException.class, cf::createConnection); // thrown by TestDriver due to missing user - } - - @Test - public void testDriverManagerWithoutPassword() { - final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", "user", (char[]) null); - assertThrows(SQLException.class, cf::createConnection); // thrown by TestDriver due to invalid password - } - - @Test - public void testDriverManagerWithoutUser() { - final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", null, "pass"); - assertThrows(IndexOutOfBoundsException.class, cf::createConnection); // thrown by TestDriver due to missing user - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.junit.jupiter.api.Test; + +/** + * This test *must* execute before all other tests to be effective as it tests the initialization of DriverManager. Based on the test case for DBCP-212 written + * by Marcos Sanz + */ +public class TestDriverManagerConnectionFactory extends AbstractDriverTest { + + private static final class ConnectionThread implements Runnable { + private final DataSource ds; + private volatile boolean result = true; + + private ConnectionThread(final DataSource ds) { + this.ds = ds; + } + + public boolean getResult() { + return result; + } + + @Override + public void run() { + Connection conn = null; + try { + conn = ds.getConnection(); + } catch (final Exception e) { + e.printStackTrace(); + result = false; + } finally { + if (conn != null) { + try { + conn.close(); + } catch (final Exception e) { + e.printStackTrace(); + result = false; + } + } + } + } + + @Override + public String toString() { + return "ConnectionThread [ds=" + ds + ", result=" + result + "]"; + } + } + + @Test + public void testDriverManagerCredentialsInUrl() throws SQLException { + final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver;user=foo;password=bar", null, + (char[]) null); + cf.createConnection(); + } + + public void testDriverManagerInit(final boolean withProperties) throws Exception { + final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); + config.setMaxTotal(10); + config.setMaxIdle(0); + final Properties properties = new Properties(); + // The names "user" and "password" are specified in + // java.sql.DriverManager.getConnection(String, String, String) + properties.setProperty(Constants.KEY_USER, "foo"); + properties.setProperty(Constants.KEY_PASSWORD, "bar"); + final ConnectionFactory connectionFactory = withProperties ? new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", properties) + : new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", "foo", "bar"); + final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); + poolableConnectionFactory.setDefaultReadOnly(Boolean.FALSE); + poolableConnectionFactory.setDefaultAutoCommit(Boolean.TRUE); + + final GenericObjectPool connectionPool = new GenericObjectPool<>(poolableConnectionFactory, config); + poolableConnectionFactory.setPool(connectionPool); + final PoolingDataSource dataSource = new PoolingDataSource<>(connectionPool); + + final ConnectionThread[] connectionThreads = new ConnectionThread[10]; + final Thread[] threads = new Thread[10]; + + for (int i = 0; i < 10; i++) { + connectionThreads[i] = new ConnectionThread(dataSource); + threads[i] = new Thread(connectionThreads[i]); + } + for (int i = 0; i < 10; i++) { + threads[i].start(); + } + for (int i = 0; i < 10; i++) { + while (threads[i].isAlive()) { // JDK1.5: getState() != Thread.State.TERMINATED) { + Thread.sleep(100); + } + if (!connectionThreads[i].getResult()) { + fail("Exception during getConnection(): " + connectionThreads[i]); + } + } + } + + @Test + public void testDriverManagerInitWithCredentials() throws Exception { + testDriverManagerInit(false); + } + + @Test + public void testDriverManagerInitWithEmptyProperties() throws Exception { + final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver;user=foo;password=bar"); + connectionFactory.createConnection(); + } + + @Test + public void testDriverManagerInitWithProperties() throws Exception { + testDriverManagerInit(true); + } + + @Test + public void testDriverManagerWithoutCredentials() { + final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", null, (char[]) null); + assertThrows(ArrayIndexOutOfBoundsException.class, cf::createConnection); // thrown by TestDriver due to missing user + } + + @Test + public void testDriverManagerWithoutPassword() { + final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", "user", (char[]) null); + assertThrows(SQLException.class, cf::createConnection); // thrown by TestDriver due to invalid password + } + + @Test + public void testDriverManagerWithoutUser() { + final DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver", null, "pass"); + assertThrows(IndexOutOfBoundsException.class, cf::createConnection); // thrown by TestDriver due to missing user + } + +} diff --git a/src/test/java/org/apache/commons/dbcp2/TestPStmtKey.java b/src/test/java/org/apache/commons/dbcp2/TestPStmtKey.java index 9a80ffc5e5..ec51ce7023 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestPStmtKey.java +++ b/src/test/java/org/apache/commons/dbcp2/TestPStmtKey.java @@ -1,262 +1,262 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.dbcp2; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.Arrays; - -import org.apache.commons.dbcp2.PoolingConnection.StatementType; -import org.junit.jupiter.api.Test; - -/** - * Tests {@link PStmtKey}. - */ -public class TestPStmtKey { - - /** - * Tests constructors with different catalog. - */ - @Test - public void testCtorDifferentCatalog() { - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog2", "schema1")); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0), - new PStmtKey("sql", "catalog2", "schema1", 0)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), - new PStmtKey("sql", "catalog2", "schema1", 0, 0)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), - new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog2", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog2", "schema1", 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), - new PStmtKey("sql", "catalog2", "schema1", (int[]) null)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), - new PStmtKey("sql", "catalog2", "schema1", new int[1])); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), - new PStmtKey("sql", "catalog2", "schema1", (String[]) null)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), - new PStmtKey("sql", "catalog2", "schema1", new String[] {"A" })); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog2", "schema1", StatementType.PREPARED_STATEMENT)); - assertNotEquals( - new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), - new PStmtKey("sql", "catalog2", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); - } - - /** - * Tests constructors with different schemas. - */ - @Test - public void testCtorDifferentSchema() { - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog1", "schema2")); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0), - new PStmtKey("sql", "catalog1", "schema2", 0)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), - new PStmtKey("sql", "catalog1", "schema2", 0, 0)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), - new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog1", "schema2", 0, 0, StatementType.CALLABLE_STATEMENT)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema2", 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), - new PStmtKey("sql", "catalog1", "schema2", (int[]) null)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), - new PStmtKey("sql", "catalog1", "schema2", new int[1])); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), - new PStmtKey("sql", "catalog1", "schema2", (String[]) null)); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), - new PStmtKey("sql", "catalog1", "schema2", new String[] {"A" })); - assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema2", StatementType.PREPARED_STATEMENT)); - assertNotEquals( - new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), - new PStmtKey("sql", "catalog1", "schema2", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); - } - - /** - * Tests constructors with different catalog. - */ - @Test - public void testCtorEquals() { - assertEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog1", "schema1")); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0), - new PStmtKey("sql", "catalog1", "schema1", 0)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), - new PStmtKey("sql", "catalog1", "schema1", 0, 0)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), - new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0)); - // - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), - new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT)); - // - assertEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), - new PStmtKey("sql", "catalog1", "schema1", (int[]) null)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), - new PStmtKey("sql", "catalog1", "schema1", new int[1])); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), - new PStmtKey("sql", "catalog1", "schema1", (String[]) null)); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), - new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" })); - assertEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), - new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT)); - assertEquals( - new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), - new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); - } - - /** - * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, int[])}. - * - * See https://issues.apache.org/jira/browse/DBCP-494 - */ - @Test - public void testCtorStringStringArrayOfInts() { - final int[] input = {0, 0}; - final PStmtKey pStmtKey = new PStmtKey("", "", "", input); - assertArrayEquals(input, pStmtKey.getColumnIndexes()); - input[0] = 1; - input[1] = 1; - assertFalse(Arrays.equals(input, pStmtKey.getColumnIndexes())); - } - - /** - * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, int[])}. - * - * See https://issues.apache.org/jira/browse/DBCP-494 - */ - @Test - public void testCtorStringStringArrayOfNullInts() { - final int[] input = null; - final PStmtKey pStmtKey = new PStmtKey("", "", "", input); - assertArrayEquals(input, pStmtKey.getColumnIndexes()); - } - - /** - * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, String[])}. - * - * See https://issues.apache.org/jira/browse/DBCP-494 - */ - @Test - public void testCtorStringStringArrayOfNullStrings() { - final String[] input = null; - final PStmtKey pStmtKey = new PStmtKey("", "", "", input); - assertArrayEquals(input, pStmtKey.getColumnNames()); - } - - /** - * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, String[])}. - * - * See https://issues.apache.org/jira/browse/DBCP-494 - */ - @Test - public void testCtorStringStringArrayOfStrings() { - final String[] input = {"A", "B"}; - final PStmtKey pStmtKey = new PStmtKey("", "", "", input); - assertArrayEquals(input, pStmtKey.getColumnNames()); - input[0] = "C"; - input[1] = "D"; - assertFalse(Arrays.equals(input, pStmtKey.getColumnNames())); - } - - @Test - public void testEquals() { - final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public", - java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, java.sql.ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT); - assertEquals(pStmtKey, pStmtKey); - assertNotEquals(null, pStmtKey); - assertNotEquals(pStmtKey, new Object()); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 2", "catalog", "public", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT)); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "anothercatalog", "public", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT)); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "private", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT)); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", - ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT)); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE, - StatementType.CALLABLE_STATEMENT)); - assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.PREPARED_STATEMENT)); - assertEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT)); - assertEquals(pStmtKey.hashCode(), new PStmtKey("SELECT 1", "catalog", "public", - java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, java.sql.ResultSet.CONCUR_READ_ONLY, - StatementType.CALLABLE_STATEMENT).hashCode()); - } - - @Test - public void testGettersSetters() { - final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public"); - assertEquals("SELECT 1", pStmtKey.getSql()); - assertEquals("public", pStmtKey.getSchema()); - assertEquals("catalog", pStmtKey.getCatalog()); - assertNull(pStmtKey.getAutoGeneratedKeys()); - assertNull(pStmtKey.getResultSetConcurrency()); - assertNull(pStmtKey.getResultSetHoldability()); - assertNull(pStmtKey.getResultSetType()); - assertEquals(StatementType.PREPARED_STATEMENT, pStmtKey.getStmtType()); - } - - @Test - public void testToString() { - final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public", - StatementType.CALLABLE_STATEMENT, Statement.RETURN_GENERATED_KEYS); - assertTrue(pStmtKey.toString().contains("sql=SELECT 1")); - assertTrue(pStmtKey.toString().contains("schema=public")); - assertTrue(pStmtKey.toString().contains("autoGeneratedKeys=1")); - assertTrue(pStmtKey.toString().contains("statementType=CALLABLE_STATEMENT")); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbcp2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Arrays; + +import org.apache.commons.dbcp2.PoolingConnection.StatementType; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PStmtKey}. + */ +public class TestPStmtKey { + + /** + * Tests constructors with different catalog. + */ + @Test + public void testCtorDifferentCatalog() { + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog2", "schema1")); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0), + new PStmtKey("sql", "catalog2", "schema1", 0)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), + new PStmtKey("sql", "catalog2", "schema1", 0, 0)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), + new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog2", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog2", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog2", "schema1", 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), + new PStmtKey("sql", "catalog2", "schema1", (int[]) null)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), + new PStmtKey("sql", "catalog2", "schema1", new int[1])); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), + new PStmtKey("sql", "catalog2", "schema1", (String[]) null)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), + new PStmtKey("sql", "catalog2", "schema1", new String[] {"A" })); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog2", "schema1", StatementType.PREPARED_STATEMENT)); + assertNotEquals( + new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), + new PStmtKey("sql", "catalog2", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); + } + + /** + * Tests constructors with different schemas. + */ + @Test + public void testCtorDifferentSchema() { + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog1", "schema2")); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0), + new PStmtKey("sql", "catalog1", "schema2", 0)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), + new PStmtKey("sql", "catalog1", "schema2", 0, 0)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), + new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema2", 0, 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog1", "schema2", 0, 0, StatementType.CALLABLE_STATEMENT)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema2", 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), + new PStmtKey("sql", "catalog1", "schema2", (int[]) null)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), + new PStmtKey("sql", "catalog1", "schema2", new int[1])); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), + new PStmtKey("sql", "catalog1", "schema2", (String[]) null)); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), + new PStmtKey("sql", "catalog1", "schema2", new String[] {"A" })); + assertNotEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema2", StatementType.PREPARED_STATEMENT)); + assertNotEquals( + new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), + new PStmtKey("sql", "catalog1", "schema2", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); + } + + /** + * Tests constructors with different catalog. + */ + @Test + public void testCtorEquals() { + assertEquals(new PStmtKey("sql", "catalog1", "schema1"), new PStmtKey("sql", "catalog1", "schema1")); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0), + new PStmtKey("sql", "catalog1", "schema1", 0)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0), + new PStmtKey("sql", "catalog1", "schema1", 0, 0)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0), + new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0)); + // + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.CALLABLE_STATEMENT)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema1", 0, 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT), + new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.CALLABLE_STATEMENT)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema1", 0, 0, StatementType.PREPARED_STATEMENT)); + // + assertEquals(new PStmtKey("sql", "catalog1", "schema1", (int[]) null), + new PStmtKey("sql", "catalog1", "schema1", (int[]) null)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", new int[1]), + new PStmtKey("sql", "catalog1", "schema1", new int[1])); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", (String[]) null), + new PStmtKey("sql", "catalog1", "schema1", (String[]) null)); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" }), + new PStmtKey("sql", "catalog1", "schema1", new String[] {"A" })); + assertEquals(new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT), + new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT)); + assertEquals( + new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE), + new PStmtKey("sql", "catalog1", "schema1", StatementType.PREPARED_STATEMENT, Integer.MAX_VALUE)); + } + + /** + * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, int[])}. + * + * See https://issues.apache.org/jira/browse/DBCP-494 + */ + @Test + public void testCtorStringStringArrayOfInts() { + final int[] input = {0, 0}; + final PStmtKey pStmtKey = new PStmtKey("", "", "", input); + assertArrayEquals(input, pStmtKey.getColumnIndexes()); + input[0] = 1; + input[1] = 1; + assertFalse(Arrays.equals(input, pStmtKey.getColumnIndexes())); + } + + /** + * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, int[])}. + * + * See https://issues.apache.org/jira/browse/DBCP-494 + */ + @Test + public void testCtorStringStringArrayOfNullInts() { + final int[] input = null; + final PStmtKey pStmtKey = new PStmtKey("", "", "", input); + assertArrayEquals(input, pStmtKey.getColumnIndexes()); + } + + /** + * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, String[])}. + * + * See https://issues.apache.org/jira/browse/DBCP-494 + */ + @Test + public void testCtorStringStringArrayOfNullStrings() { + final String[] input = null; + final PStmtKey pStmtKey = new PStmtKey("", "", "", input); + assertArrayEquals(input, pStmtKey.getColumnNames()); + } + + /** + * Tests {@link org.apache.commons.dbcp2.PStmtKey#PStmtKey(String, String, String, String[])}. + * + * See https://issues.apache.org/jira/browse/DBCP-494 + */ + @Test + public void testCtorStringStringArrayOfStrings() { + final String[] input = {"A", "B"}; + final PStmtKey pStmtKey = new PStmtKey("", "", "", input); + assertArrayEquals(input, pStmtKey.getColumnNames()); + input[0] = "C"; + input[1] = "D"; + assertFalse(Arrays.equals(input, pStmtKey.getColumnNames())); + } + + @Test + public void testEquals() { + final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public", + java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, java.sql.ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT); + assertEquals(pStmtKey, pStmtKey); + assertNotEquals(null, pStmtKey); + assertNotEquals(pStmtKey, new Object()); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 2", "catalog", "public", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT)); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "anothercatalog", "public", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT)); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "private", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT)); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", + ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT)); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE, + StatementType.CALLABLE_STATEMENT)); + assertNotEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.PREPARED_STATEMENT)); + assertEquals(pStmtKey, new PStmtKey("SELECT 1", "catalog", "public", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT)); + assertEquals(pStmtKey.hashCode(), new PStmtKey("SELECT 1", "catalog", "public", + java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, java.sql.ResultSet.CONCUR_READ_ONLY, + StatementType.CALLABLE_STATEMENT).hashCode()); + } + + @Test + public void testGettersSetters() { + final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public"); + assertEquals("SELECT 1", pStmtKey.getSql()); + assertEquals("public", pStmtKey.getSchema()); + assertEquals("catalog", pStmtKey.getCatalog()); + assertNull(pStmtKey.getAutoGeneratedKeys()); + assertNull(pStmtKey.getResultSetConcurrency()); + assertNull(pStmtKey.getResultSetHoldability()); + assertNull(pStmtKey.getResultSetType()); + assertEquals(StatementType.PREPARED_STATEMENT, pStmtKey.getStmtType()); + } + + @Test + public void testToString() { + final PStmtKey pStmtKey = new PStmtKey("SELECT 1", "catalog", "public", + StatementType.CALLABLE_STATEMENT, Statement.RETURN_GENERATED_KEYS); + assertTrue(pStmtKey.toString().contains("sql=SELECT 1")); + assertTrue(pStmtKey.toString().contains("schema=public")); + assertTrue(pStmtKey.toString().contains("autoGeneratedKeys=1")); + assertTrue(pStmtKey.toString().contains("statementType=CALLABLE_STATEMENT")); + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/TesterResultSet.java b/src/test/java/org/apache/commons/dbcp2/TesterResultSet.java index a4d0ccdee8..8bc91ef037 100644 --- a/src/test/java/org/apache/commons/dbcp2/TesterResultSet.java +++ b/src/test/java/org/apache/commons/dbcp2/TesterResultSet.java @@ -1,1168 +1,1168 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2; - -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.NClob; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.RowId; -import java.sql.SQLException; -import java.sql.SQLType; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Statement; -import java.util.Calendar; -import java.util.Map; - -/** - * A dummy {@link ResultSet}, for testing purposes. - */ -public class TesterResultSet extends AbandonedTrace implements ResultSet { - - protected int type = ResultSet.TYPE_FORWARD_ONLY; - protected int concurrency = ResultSet.CONCUR_READ_ONLY; - protected Object[][] data; - protected int currentRow = -1; - protected Statement statement; - protected int rowsLeft = 2; - protected boolean open = true; - protected boolean sqlExceptionOnClose; - - public TesterResultSet(final Statement statement) { - this.statement = statement; - } - - public TesterResultSet(final Statement statement, final int type, final int concurrency) { - this.statement = statement; - this.data = null; - this.type = type; - this.concurrency = concurrency; - } - - public TesterResultSet(final Statement statement, final Object[][] data) { - this.statement = statement; - this.data = data; - } - - @Override - public boolean absolute(final int row) throws SQLException { - checkOpen(); - return false; - } - - @Override - public void afterLast() throws SQLException { - checkOpen(); - } - - @Override - public void beforeFirst() throws SQLException { - checkOpen(); - } - - @Override - public void cancelRowUpdates() throws SQLException { - checkOpen(); - } - - protected void checkOpen() throws SQLException { - if (!open) { - throw new SQLException("ResultSet is closed."); - } - } - - @Override - public void clearWarnings() throws SQLException { - checkOpen(); - } - - @Override - public void close() throws SQLException { - if (sqlExceptionOnClose) { - throw new SQLException("TestSQLExceptionOnClose"); - } - - if (!open) { - return; - } - - // Not all result sets are generated from statements eg DatabaseMetaData - if (statement != null) { - ((TesterStatement) statement).resultSet = null; - } - - open = false; - } - - @Override - public void deleteRow() throws SQLException { - checkOpen(); - } - - @Override - public int findColumn(final String columnName) throws SQLException { - checkOpen(); - return 1; - } - - @Override - public boolean first() throws SQLException { - checkOpen(); - return false; - } - - @Override - public Array getArray(final int i) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Array getArray(final String colName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.io.InputStream getAsciiStream(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.io.InputStream getAsciiStream(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public BigDecimal getBigDecimal(final int columnIndex) throws SQLException { - checkOpen(); - return new BigDecimal(columnIndex); - } - - /** @deprecated */ - @Deprecated - @Override - public BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException { - checkOpen(); - return new BigDecimal(columnIndex); - } - - @Override - public BigDecimal getBigDecimal(final String columnName) throws SQLException { - checkOpen(); - return new BigDecimal(columnName.hashCode()); - } - - /** @deprecated */ - @Deprecated - @Override - public BigDecimal getBigDecimal(final String columnName, final int scale) throws SQLException { - checkOpen(); - return new BigDecimal(columnName.hashCode()); - } - - @Override - public java.io.InputStream getBinaryStream(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.io.InputStream getBinaryStream(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Blob getBlob(final int i) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Blob getBlob(final String colName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public boolean getBoolean(final int columnIndex) throws SQLException { - checkOpen(); - return true; - } - - @Override - public boolean getBoolean(final String columnName) throws SQLException { - checkOpen(); - return true; - } - - @Override - public byte getByte(final int columnIndex) throws SQLException { - checkOpen(); - return (byte) columnIndex; - } - - @Override - public byte getByte(final String columnName) throws SQLException { - checkOpen(); - return (byte) columnName.hashCode(); - } - - @Override - public byte[] getBytes(final int columnIndex) throws SQLException { - checkOpen(); - return new byte[] { (byte) columnIndex }; - } - - @Override - public byte[] getBytes(final String columnName) throws SQLException { - checkOpen(); - return columnName.getBytes(StandardCharsets.UTF_8); - } - - @Override - public java.io.Reader getCharacterStream(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.io.Reader getCharacterStream(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Clob getClob(final int i) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Clob getClob(final String colName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public int getConcurrency() throws SQLException { - return this.concurrency; - } - - @Override - public String getCursorName() throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Date getDate(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Date getDate(final int columnIndex, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Date getDate(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Date getDate(final String columnName, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public double getDouble(final int columnIndex) throws SQLException { - checkOpen(); - return columnIndex; - } - - @Override - public double getDouble(final String columnName) throws SQLException { - checkOpen(); - return columnName.hashCode(); - } - - @Override - public int getFetchDirection() throws SQLException { - checkOpen(); - return 1; - } - - @Override - public int getFetchSize() throws SQLException { - checkOpen(); - return 2; - } - - @Override - public float getFloat(final int columnIndex) throws SQLException { - checkOpen(); - return columnIndex; - } - - @Override - public float getFloat(final String columnName) throws SQLException { - checkOpen(); - return columnName.hashCode(); - } - - @Override - public int getHoldability() throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public int getInt(final int columnIndex) throws SQLException { - checkOpen(); - return (short) columnIndex; - } - - @Override - public int getInt(final String columnName) throws SQLException { - checkOpen(); - return columnName.hashCode(); - } - - @Override - public long getLong(final int columnIndex) throws SQLException { - checkOpen(); - return columnIndex; - } - - @Override - public long getLong(final String columnName) throws SQLException { - checkOpen(); - return columnName.hashCode(); - } - - @Override - public ResultSetMetaData getMetaData() throws SQLException { - checkOpen(); - return null; - } - - @Override - public Reader getNCharacterStream(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public Reader getNCharacterStream(final String columnLabel) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public NClob getNClob(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public NClob getNClob(final String columnLabel) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public String getNString(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public String getNString(final String columnLabel) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public Object getObject(final int columnIndex) throws SQLException { - checkOpen(); - if (data != null) { - return data[currentRow][columnIndex - 1]; - } - return new Object(); - } - - @Override - public T getObject(final int columnIndex, final Class type) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public Object getObject(final int i, final Map> map) throws SQLException { - checkOpen(); - return new Object(); - } - - @Override - public Object getObject(final String columnName) throws SQLException { - checkOpen(); - return columnName; - } - - @Override - public T getObject(final String columnLabel, final Class type) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public Object getObject(final String colName, final Map> map) throws SQLException { - checkOpen(); - return colName; - } - - @Override - public Ref getRef(final int i) throws SQLException { - checkOpen(); - return null; - } - - @Override - public Ref getRef(final String colName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public int getRow() throws SQLException { - checkOpen(); - return 3 - rowsLeft; - } - - @Override - public RowId getRowId(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public RowId getRowId(final String columnLabel) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public short getShort(final int columnIndex) throws SQLException { - checkOpen(); - return (short) columnIndex; - } - - @Override - public short getShort(final String columnName) throws SQLException { - checkOpen(); - return (short) columnName.hashCode(); - } - - @Override - public SQLXML getSQLXML(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public SQLXML getSQLXML(final String columnLabel) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public Statement getStatement() throws SQLException { - checkOpen(); - return statement; - } - - @Override - public String getString(final int columnIndex) throws SQLException { - checkOpen(); - if (columnIndex == -1) { - throw new SQLException("broken connection"); - } - if (data != null) { - return (String) getObject(columnIndex); - } - return "String" + columnIndex; - } - - @Override - public String getString(final String columnName) throws SQLException { - checkOpen(); - return columnName; - } - - @Override - public java.sql.Time getTime(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Time getTime(final int columnIndex, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Time getTime(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Time getTime(final String columnName, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Timestamp getTimestamp(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Timestamp getTimestamp(final int columnIndex, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Timestamp getTimestamp(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.sql.Timestamp getTimestamp(final String columnName, final Calendar cal) throws SQLException { - checkOpen(); - return null; - } - - @Override - public int getType() throws SQLException { - return this.type; - } - - /** @deprecated */ - @Deprecated - @Override - public java.io.InputStream getUnicodeStream(final int columnIndex) throws SQLException { - checkOpen(); - return null; - } - - /** @deprecated */ - @Deprecated - @Override - public java.io.InputStream getUnicodeStream(final String columnName) throws SQLException { - checkOpen(); - return null; - } - - @Override - public java.net.URL getURL(final int columnIndex) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public java.net.URL getURL(final String columnName) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public SQLWarning getWarnings() throws SQLException { - checkOpen(); - return null; - } - - @Override - public void insertRow() throws SQLException { - checkOpen(); - } - - @Override - public boolean isAfterLast() throws SQLException { - checkOpen(); - return rowsLeft < 0; - } - - @Override - public boolean isBeforeFirst() throws SQLException { - checkOpen(); - return rowsLeft == 2; - } - - @Override - public boolean isClosed() throws SQLException { - return !open; - } - - @Override - public boolean isFirst() throws SQLException { - checkOpen(); - return rowsLeft == 1; - } - - @Override - public boolean isLast() throws SQLException { - checkOpen(); - return rowsLeft == 0; - } - - public boolean isSqlExceptionOnClose() { - return sqlExceptionOnClose; - } - - @Override - public boolean isWrapperFor(final Class iface) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public boolean last() throws SQLException { - checkOpen(); - return false; - } - - @Override - public void moveToCurrentRow() throws SQLException { - checkOpen(); - } - - @Override - public void moveToInsertRow() throws SQLException { - checkOpen(); - } - - @Override - public boolean next() throws SQLException { - checkOpen(); - if (data != null) { - currentRow++; - return currentRow < data.length; - } - return --rowsLeft > 0; - } - - @Override - public boolean previous() throws SQLException { - checkOpen(); - return false; - } - - @Override - public void refreshRow() throws SQLException { - checkOpen(); - } - - @Override - public boolean relative(final int rows) throws SQLException { - checkOpen(); - return false; - } - - @Override - public boolean rowDeleted() throws SQLException { - checkOpen(); - return false; - } - - @Override - public boolean rowInserted() throws SQLException { - checkOpen(); - return false; - } - - @Override - public boolean rowUpdated() throws SQLException { - checkOpen(); - return false; - } - - @Override - public void setFetchDirection(final int direction) throws SQLException { - checkOpen(); - } - - @Override - public void setFetchSize(final int rows) throws SQLException { - checkOpen(); - } - - public void setSqlExceptionOnClose(final boolean sqlExceptionOnClose) { - this.sqlExceptionOnClose = sqlExceptionOnClose; - } - - @Override - public T unwrap(final Class iface) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateArray(final int columnIndex, final java.sql.Array x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateArray(final String columnName, final java.sql.Array x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateAsciiStream(final int columnIndex, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateAsciiStream(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateAsciiStream(final int columnIndex, final java.io.InputStream x, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateAsciiStream(final String columnLabel, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateAsciiStream(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateAsciiStream(final String columnName, final java.io.InputStream x, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateBigDecimal(final int columnIndex, final BigDecimal x) throws SQLException { - checkOpen(); - } - - @Override - public void updateBigDecimal(final String columnName, final BigDecimal x) throws SQLException { - checkOpen(); - } - - @Override - public void updateBinaryStream(final int columnIndex, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBinaryStream(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBinaryStream(final int columnIndex, final java.io.InputStream x, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateBinaryStream(final String columnLabel, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBinaryStream(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBinaryStream(final String columnName, final java.io.InputStream x, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateBlob(final int columnIndex, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBlob(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBlob(final int columnIndex, final java.sql.Blob x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBlob(final String columnLabel, final InputStream inputStream) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBlob(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBlob(final String columnName, final java.sql.Blob x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateBoolean(final int columnIndex, final boolean x) throws SQLException { - checkOpen(); - } - - @Override - public void updateBoolean(final String columnName, final boolean x) throws SQLException { - checkOpen(); - } - - @Override - public void updateByte(final int columnIndex, final byte x) throws SQLException { - checkOpen(); - } - - @Override - public void updateByte(final String columnName, final byte x) throws SQLException { - checkOpen(); - } - - @Override - public void updateBytes(final int columnIndex, final byte[] x) throws SQLException { - checkOpen(); - } - - @Override - public void updateBytes(final String columnName, final byte[] x) throws SQLException { - checkOpen(); - } - - @Override - public void updateCharacterStream(final int columnIndex, final java.io.Reader x, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateCharacterStream(final int columnIndex, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateCharacterStream(final int columnIndex, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateCharacterStream(final String columnName, final java.io.Reader reader, final int length) throws SQLException { - checkOpen(); - } - - @Override - public void updateCharacterStream(final String columnLabel, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateCharacterStream(final String columnLabel, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final int columnIndex, final java.sql.Clob x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final int columnIndex, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final int columnIndex, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final String columnName, final java.sql.Clob x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final String columnLabel, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateClob(final String columnLabel, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateDate(final int columnIndex, final java.sql.Date x) throws SQLException { - checkOpen(); - } - - @Override - public void updateDate(final String columnName, final java.sql.Date x) throws SQLException { - checkOpen(); - } - - @Override - public void updateDouble(final int columnIndex, final double x) throws SQLException { - checkOpen(); - } - - @Override - public void updateDouble(final String columnName, final double x) throws SQLException { - checkOpen(); - } - - @Override - public void updateFloat(final int columnIndex, final float x) throws SQLException { - checkOpen(); - } - - @Override - public void updateFloat(final String columnName, final float x) throws SQLException { - checkOpen(); - } - - @Override - public void updateInt(final int columnIndex, final int x) throws SQLException { - checkOpen(); - } - - @Override - public void updateInt(final String columnName, final int x) throws SQLException { - checkOpen(); - } - - @Override - public void updateLong(final int columnIndex, final long x) throws SQLException { - checkOpen(); - } - - @Override - public void updateLong(final String columnName, final long x) throws SQLException { - checkOpen(); - } - - @Override - public void updateNCharacterStream(final int columnIndex, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNCharacterStream(final int columnIndex, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNCharacterStream(final String columnLabel, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNCharacterStream(final String columnLabel, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final int columnIndex, final NClob value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final int columnIndex, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final int columnIndex, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final String columnLabel, final NClob value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final String columnLabel, final Reader reader) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNClob(final String columnLabel, final Reader reader, final long length) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNString(final int columnIndex, final String value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNString(final String columnLabel, final String value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateNull(final int columnIndex) throws SQLException { - checkOpen(); - } - - @Override - public void updateNull(final String columnName) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final int columnIndex, final Object x) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final int columnIndex, final Object x, final int scale) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final int columnIndex, final Object x, final SQLType targetSqlType) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final int columnIndex, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final String columnName, final Object x) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final String columnName, final Object x, final int scale) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final String columnLabel, final Object x, final SQLType targetSqlType) throws SQLException { - checkOpen(); - } - - @Override - public void updateObject(final String columnLabel, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { - checkOpen(); - } - - @Override - public void updateRef(final int columnIndex, final java.sql.Ref x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateRef(final String columnName, final java.sql.Ref x) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateRow() throws SQLException { - checkOpen(); - } - - @Override - public void updateRowId(final int columnIndex, final RowId value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateRowId(final String columnLabel, final RowId value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateShort(final int columnIndex, final short x) throws SQLException { - checkOpen(); - } - - @Override - public void updateShort(final String columnName, final short x) throws SQLException { - checkOpen(); - } - - @Override - public void updateSQLXML(final int columnIndex, final SQLXML value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateSQLXML(final String columnLabel, final SQLXML value) throws SQLException { - throw new SQLException("Not implemented."); - } - - @Override - public void updateString(final int columnIndex, final String x) throws SQLException { - checkOpen(); - } - - @Override - public void updateString(final String columnName, final String x) throws SQLException { - checkOpen(); - } - - @Override - public void updateTime(final int columnIndex, final java.sql.Time x) throws SQLException { - checkOpen(); - } - - @Override - public void updateTime(final String columnName, final java.sql.Time x) throws SQLException { - checkOpen(); - } - - @Override - public void updateTimestamp(final int columnIndex, final java.sql.Timestamp x) throws SQLException { - checkOpen(); - } - - @Override - public void updateTimestamp(final String columnName, final java.sql.Timestamp x) throws SQLException { - checkOpen(); - } - - @Override - public boolean wasNull() throws SQLException { - checkOpen(); - return false; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.util.Calendar; +import java.util.Map; + +/** + * A dummy {@link ResultSet}, for testing purposes. + */ +public class TesterResultSet extends AbandonedTrace implements ResultSet { + + protected int type = ResultSet.TYPE_FORWARD_ONLY; + protected int concurrency = ResultSet.CONCUR_READ_ONLY; + protected Object[][] data; + protected int currentRow = -1; + protected Statement statement; + protected int rowsLeft = 2; + protected boolean open = true; + protected boolean sqlExceptionOnClose; + + public TesterResultSet(final Statement statement) { + this.statement = statement; + } + + public TesterResultSet(final Statement statement, final int type, final int concurrency) { + this.statement = statement; + this.data = null; + this.type = type; + this.concurrency = concurrency; + } + + public TesterResultSet(final Statement statement, final Object[][] data) { + this.statement = statement; + this.data = data; + } + + @Override + public boolean absolute(final int row) throws SQLException { + checkOpen(); + return false; + } + + @Override + public void afterLast() throws SQLException { + checkOpen(); + } + + @Override + public void beforeFirst() throws SQLException { + checkOpen(); + } + + @Override + public void cancelRowUpdates() throws SQLException { + checkOpen(); + } + + protected void checkOpen() throws SQLException { + if (!open) { + throw new SQLException("ResultSet is closed."); + } + } + + @Override + public void clearWarnings() throws SQLException { + checkOpen(); + } + + @Override + public void close() throws SQLException { + if (sqlExceptionOnClose) { + throw new SQLException("TestSQLExceptionOnClose"); + } + + if (!open) { + return; + } + + // Not all result sets are generated from statements eg DatabaseMetaData + if (statement != null) { + ((TesterStatement) statement).resultSet = null; + } + + open = false; + } + + @Override + public void deleteRow() throws SQLException { + checkOpen(); + } + + @Override + public int findColumn(final String columnName) throws SQLException { + checkOpen(); + return 1; + } + + @Override + public boolean first() throws SQLException { + checkOpen(); + return false; + } + + @Override + public Array getArray(final int i) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Array getArray(final String colName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.io.InputStream getAsciiStream(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.io.InputStream getAsciiStream(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public BigDecimal getBigDecimal(final int columnIndex) throws SQLException { + checkOpen(); + return new BigDecimal(columnIndex); + } + + /** @deprecated */ + @Deprecated + @Override + public BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException { + checkOpen(); + return new BigDecimal(columnIndex); + } + + @Override + public BigDecimal getBigDecimal(final String columnName) throws SQLException { + checkOpen(); + return new BigDecimal(columnName.hashCode()); + } + + /** @deprecated */ + @Deprecated + @Override + public BigDecimal getBigDecimal(final String columnName, final int scale) throws SQLException { + checkOpen(); + return new BigDecimal(columnName.hashCode()); + } + + @Override + public java.io.InputStream getBinaryStream(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.io.InputStream getBinaryStream(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Blob getBlob(final int i) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Blob getBlob(final String colName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public boolean getBoolean(final int columnIndex) throws SQLException { + checkOpen(); + return true; + } + + @Override + public boolean getBoolean(final String columnName) throws SQLException { + checkOpen(); + return true; + } + + @Override + public byte getByte(final int columnIndex) throws SQLException { + checkOpen(); + return (byte) columnIndex; + } + + @Override + public byte getByte(final String columnName) throws SQLException { + checkOpen(); + return (byte) columnName.hashCode(); + } + + @Override + public byte[] getBytes(final int columnIndex) throws SQLException { + checkOpen(); + return new byte[] { (byte) columnIndex }; + } + + @Override + public byte[] getBytes(final String columnName) throws SQLException { + checkOpen(); + return columnName.getBytes(StandardCharsets.UTF_8); + } + + @Override + public java.io.Reader getCharacterStream(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.io.Reader getCharacterStream(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Clob getClob(final int i) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Clob getClob(final String colName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public int getConcurrency() throws SQLException { + return this.concurrency; + } + + @Override + public String getCursorName() throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Date getDate(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Date getDate(final int columnIndex, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Date getDate(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Date getDate(final String columnName, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public double getDouble(final int columnIndex) throws SQLException { + checkOpen(); + return columnIndex; + } + + @Override + public double getDouble(final String columnName) throws SQLException { + checkOpen(); + return columnName.hashCode(); + } + + @Override + public int getFetchDirection() throws SQLException { + checkOpen(); + return 1; + } + + @Override + public int getFetchSize() throws SQLException { + checkOpen(); + return 2; + } + + @Override + public float getFloat(final int columnIndex) throws SQLException { + checkOpen(); + return columnIndex; + } + + @Override + public float getFloat(final String columnName) throws SQLException { + checkOpen(); + return columnName.hashCode(); + } + + @Override + public int getHoldability() throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public int getInt(final int columnIndex) throws SQLException { + checkOpen(); + return (short) columnIndex; + } + + @Override + public int getInt(final String columnName) throws SQLException { + checkOpen(); + return columnName.hashCode(); + } + + @Override + public long getLong(final int columnIndex) throws SQLException { + checkOpen(); + return columnIndex; + } + + @Override + public long getLong(final String columnName) throws SQLException { + checkOpen(); + return columnName.hashCode(); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + checkOpen(); + return null; + } + + @Override + public Reader getNCharacterStream(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public Reader getNCharacterStream(final String columnLabel) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public NClob getNClob(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public NClob getNClob(final String columnLabel) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public String getNString(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public String getNString(final String columnLabel) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public Object getObject(final int columnIndex) throws SQLException { + checkOpen(); + if (data != null) { + return data[currentRow][columnIndex - 1]; + } + return new Object(); + } + + @Override + public T getObject(final int columnIndex, final Class type) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public Object getObject(final int i, final Map> map) throws SQLException { + checkOpen(); + return new Object(); + } + + @Override + public Object getObject(final String columnName) throws SQLException { + checkOpen(); + return columnName; + } + + @Override + public T getObject(final String columnLabel, final Class type) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public Object getObject(final String colName, final Map> map) throws SQLException { + checkOpen(); + return colName; + } + + @Override + public Ref getRef(final int i) throws SQLException { + checkOpen(); + return null; + } + + @Override + public Ref getRef(final String colName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public int getRow() throws SQLException { + checkOpen(); + return 3 - rowsLeft; + } + + @Override + public RowId getRowId(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public RowId getRowId(final String columnLabel) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public short getShort(final int columnIndex) throws SQLException { + checkOpen(); + return (short) columnIndex; + } + + @Override + public short getShort(final String columnName) throws SQLException { + checkOpen(); + return (short) columnName.hashCode(); + } + + @Override + public SQLXML getSQLXML(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public SQLXML getSQLXML(final String columnLabel) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public Statement getStatement() throws SQLException { + checkOpen(); + return statement; + } + + @Override + public String getString(final int columnIndex) throws SQLException { + checkOpen(); + if (columnIndex == -1) { + throw new SQLException("broken connection"); + } + if (data != null) { + return (String) getObject(columnIndex); + } + return "String" + columnIndex; + } + + @Override + public String getString(final String columnName) throws SQLException { + checkOpen(); + return columnName; + } + + @Override + public java.sql.Time getTime(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Time getTime(final int columnIndex, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Time getTime(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Time getTime(final String columnName, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Timestamp getTimestamp(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Timestamp getTimestamp(final int columnIndex, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Timestamp getTimestamp(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.sql.Timestamp getTimestamp(final String columnName, final Calendar cal) throws SQLException { + checkOpen(); + return null; + } + + @Override + public int getType() throws SQLException { + return this.type; + } + + /** @deprecated */ + @Deprecated + @Override + public java.io.InputStream getUnicodeStream(final int columnIndex) throws SQLException { + checkOpen(); + return null; + } + + /** @deprecated */ + @Deprecated + @Override + public java.io.InputStream getUnicodeStream(final String columnName) throws SQLException { + checkOpen(); + return null; + } + + @Override + public java.net.URL getURL(final int columnIndex) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public java.net.URL getURL(final String columnName) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + checkOpen(); + return null; + } + + @Override + public void insertRow() throws SQLException { + checkOpen(); + } + + @Override + public boolean isAfterLast() throws SQLException { + checkOpen(); + return rowsLeft < 0; + } + + @Override + public boolean isBeforeFirst() throws SQLException { + checkOpen(); + return rowsLeft == 2; + } + + @Override + public boolean isClosed() throws SQLException { + return !open; + } + + @Override + public boolean isFirst() throws SQLException { + checkOpen(); + return rowsLeft == 1; + } + + @Override + public boolean isLast() throws SQLException { + checkOpen(); + return rowsLeft == 0; + } + + public boolean isSqlExceptionOnClose() { + return sqlExceptionOnClose; + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public boolean last() throws SQLException { + checkOpen(); + return false; + } + + @Override + public void moveToCurrentRow() throws SQLException { + checkOpen(); + } + + @Override + public void moveToInsertRow() throws SQLException { + checkOpen(); + } + + @Override + public boolean next() throws SQLException { + checkOpen(); + if (data != null) { + currentRow++; + return currentRow < data.length; + } + return --rowsLeft > 0; + } + + @Override + public boolean previous() throws SQLException { + checkOpen(); + return false; + } + + @Override + public void refreshRow() throws SQLException { + checkOpen(); + } + + @Override + public boolean relative(final int rows) throws SQLException { + checkOpen(); + return false; + } + + @Override + public boolean rowDeleted() throws SQLException { + checkOpen(); + return false; + } + + @Override + public boolean rowInserted() throws SQLException { + checkOpen(); + return false; + } + + @Override + public boolean rowUpdated() throws SQLException { + checkOpen(); + return false; + } + + @Override + public void setFetchDirection(final int direction) throws SQLException { + checkOpen(); + } + + @Override + public void setFetchSize(final int rows) throws SQLException { + checkOpen(); + } + + public void setSqlExceptionOnClose(final boolean sqlExceptionOnClose) { + this.sqlExceptionOnClose = sqlExceptionOnClose; + } + + @Override + public T unwrap(final Class iface) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateArray(final int columnIndex, final java.sql.Array x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateArray(final String columnName, final java.sql.Array x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateAsciiStream(final int columnIndex, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateAsciiStream(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateAsciiStream(final int columnIndex, final java.io.InputStream x, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateAsciiStream(final String columnLabel, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateAsciiStream(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateAsciiStream(final String columnName, final java.io.InputStream x, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateBigDecimal(final int columnIndex, final BigDecimal x) throws SQLException { + checkOpen(); + } + + @Override + public void updateBigDecimal(final String columnName, final BigDecimal x) throws SQLException { + checkOpen(); + } + + @Override + public void updateBinaryStream(final int columnIndex, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBinaryStream(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBinaryStream(final int columnIndex, final java.io.InputStream x, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateBinaryStream(final String columnLabel, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBinaryStream(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBinaryStream(final String columnName, final java.io.InputStream x, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateBlob(final int columnIndex, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBlob(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBlob(final int columnIndex, final java.sql.Blob x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBlob(final String columnLabel, final InputStream inputStream) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBlob(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBlob(final String columnName, final java.sql.Blob x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateBoolean(final int columnIndex, final boolean x) throws SQLException { + checkOpen(); + } + + @Override + public void updateBoolean(final String columnName, final boolean x) throws SQLException { + checkOpen(); + } + + @Override + public void updateByte(final int columnIndex, final byte x) throws SQLException { + checkOpen(); + } + + @Override + public void updateByte(final String columnName, final byte x) throws SQLException { + checkOpen(); + } + + @Override + public void updateBytes(final int columnIndex, final byte[] x) throws SQLException { + checkOpen(); + } + + @Override + public void updateBytes(final String columnName, final byte[] x) throws SQLException { + checkOpen(); + } + + @Override + public void updateCharacterStream(final int columnIndex, final java.io.Reader x, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateCharacterStream(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateCharacterStream(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateCharacterStream(final String columnName, final java.io.Reader reader, final int length) throws SQLException { + checkOpen(); + } + + @Override + public void updateCharacterStream(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateCharacterStream(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final int columnIndex, final java.sql.Clob x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final String columnName, final java.sql.Clob x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateClob(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateDate(final int columnIndex, final java.sql.Date x) throws SQLException { + checkOpen(); + } + + @Override + public void updateDate(final String columnName, final java.sql.Date x) throws SQLException { + checkOpen(); + } + + @Override + public void updateDouble(final int columnIndex, final double x) throws SQLException { + checkOpen(); + } + + @Override + public void updateDouble(final String columnName, final double x) throws SQLException { + checkOpen(); + } + + @Override + public void updateFloat(final int columnIndex, final float x) throws SQLException { + checkOpen(); + } + + @Override + public void updateFloat(final String columnName, final float x) throws SQLException { + checkOpen(); + } + + @Override + public void updateInt(final int columnIndex, final int x) throws SQLException { + checkOpen(); + } + + @Override + public void updateInt(final String columnName, final int x) throws SQLException { + checkOpen(); + } + + @Override + public void updateLong(final int columnIndex, final long x) throws SQLException { + checkOpen(); + } + + @Override + public void updateLong(final String columnName, final long x) throws SQLException { + checkOpen(); + } + + @Override + public void updateNCharacterStream(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNCharacterStream(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNCharacterStream(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNCharacterStream(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final int columnIndex, final NClob value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final String columnLabel, final NClob value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNClob(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNString(final int columnIndex, final String value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNString(final String columnLabel, final String value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateNull(final int columnIndex) throws SQLException { + checkOpen(); + } + + @Override + public void updateNull(final String columnName) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final int columnIndex, final Object x) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final int columnIndex, final Object x, final int scale) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final int columnIndex, final Object x, final SQLType targetSqlType) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final int columnIndex, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final String columnName, final Object x) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final String columnName, final Object x, final int scale) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final String columnLabel, final Object x, final SQLType targetSqlType) throws SQLException { + checkOpen(); + } + + @Override + public void updateObject(final String columnLabel, final Object x, final SQLType targetSqlType, final int scaleOrLength) throws SQLException { + checkOpen(); + } + + @Override + public void updateRef(final int columnIndex, final java.sql.Ref x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateRef(final String columnName, final java.sql.Ref x) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateRow() throws SQLException { + checkOpen(); + } + + @Override + public void updateRowId(final int columnIndex, final RowId value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateRowId(final String columnLabel, final RowId value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateShort(final int columnIndex, final short x) throws SQLException { + checkOpen(); + } + + @Override + public void updateShort(final String columnName, final short x) throws SQLException { + checkOpen(); + } + + @Override + public void updateSQLXML(final int columnIndex, final SQLXML value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateSQLXML(final String columnLabel, final SQLXML value) throws SQLException { + throw new SQLException("Not implemented."); + } + + @Override + public void updateString(final int columnIndex, final String x) throws SQLException { + checkOpen(); + } + + @Override + public void updateString(final String columnName, final String x) throws SQLException { + checkOpen(); + } + + @Override + public void updateTime(final int columnIndex, final java.sql.Time x) throws SQLException { + checkOpen(); + } + + @Override + public void updateTime(final String columnName, final java.sql.Time x) throws SQLException { + checkOpen(); + } + + @Override + public void updateTimestamp(final int columnIndex, final java.sql.Timestamp x) throws SQLException { + checkOpen(); + } + + @Override + public void updateTimestamp(final String columnName, final java.sql.Timestamp x) throws SQLException { + checkOpen(); + } + + @Override + public boolean wasNull() throws SQLException { + checkOpen(); + return false; + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/cpdsadapter/TestDriverAdapterCPDS.java b/src/test/java/org/apache/commons/dbcp2/cpdsadapter/TestDriverAdapterCPDS.java index 3400220442..2d969829f7 100644 --- a/src/test/java/org/apache/commons/dbcp2/cpdsadapter/TestDriverAdapterCPDS.java +++ b/src/test/java/org/apache/commons/dbcp2/cpdsadapter/TestDriverAdapterCPDS.java @@ -1,446 +1,446 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2.cpdsadapter; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.PrintWriter; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.Statement; -import java.time.Duration; -import java.util.Properties; - -import javax.naming.NamingException; -import javax.naming.Reference; -import javax.naming.StringRefAddr; -import javax.sql.DataSource; - -import org.apache.commons.dbcp2.Constants; -import org.apache.commons.dbcp2.DelegatingPreparedStatement; -import org.apache.commons.dbcp2.DelegatingStatement; -import org.apache.commons.dbcp2.PStmtKey; -import org.apache.commons.dbcp2.PoolablePreparedStatement; -import org.apache.commons.dbcp2.TestUtils; -import org.apache.commons.dbcp2.datasources.SharedPoolDataSource; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Tests for DriverAdapterCPDS - */ -public class TestDriverAdapterCPDS { - - private static final class ThreadDbcp367 extends Thread { - - private final DataSource dataSource; - - private volatile boolean failed; - - public ThreadDbcp367(final DataSource dataSource) { - this.dataSource = dataSource; - } - - public boolean isFailed() { - return failed; - } - - @Override - public void run() { - Connection conn = null; - try { - for (int j = 0; j < 5000; j++) { - conn = dataSource.getConnection(); - conn.close(); - } - } catch (final SQLException sqle) { - failed = true; - sqle.printStackTrace(); - } - } - } - - @SuppressWarnings("resource") - private static void checkAfterClose(final Connection element, final PStmtKey pStmtKey) throws SQLException { - final ConnectionImpl connectionImpl = (ConnectionImpl) element; - assertNull(connectionImpl.getInnermostDelegate()); - assertNotNull(connectionImpl.getInnermostDelegateInternal()); - final PooledConnectionImpl pooledConnectionImpl = connectionImpl.getPooledConnectionImpl(); - assertNotNull(pooledConnectionImpl); - // Simulate released resources, should not throw NPEs - pooledConnectionImpl.destroyObject(pStmtKey, null); - pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(null)); - pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(new DelegatingPreparedStatement(null, null))); - } - - private DriverAdapterCPDS pcds; - - @BeforeEach - public void setUp() throws Exception { - pcds = new DriverAdapterCPDS(); - pcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); - pcds.setUrl("jdbc:apache:commons:testdriver"); - pcds.setUser("foo"); - pcds.setPassword("bar"); - pcds.setPoolPreparedStatements(true); - } - - @Test - public void testClose() - throws Exception { - final Connection[] c = new Connection[10]; - for (int i = 0; i < c.length; i++) { - c[i] = pcds.getPooledConnection().getConnection(); - } - - // close one of the connections - c[0].close(); - assertTrue(c[0].isClosed()); - // get a new connection - c[0] = pcds.getPooledConnection().getConnection(); - - for (final Connection element : c) { - element.close(); - checkAfterClose(element, null); - } - - // open all the connections - for (int i = 0; i < c.length; i++) { - c[i] = pcds.getPooledConnection().getConnection(); - } - for (final Connection element : c) { - element.close(); - checkAfterClose(element, null); - } - } - - @Test - public void testCloseWithUserName() - throws Exception { - final Connection[] c = new Connection[10]; - for (int i = 0; i < c.length; i++) { - c[i] = pcds.getPooledConnection("u1", "p1").getConnection(); - } - - // close one of the connections - c[0].close(); - assertTrue(c[0].isClosed()); - // get a new connection - c[0] = pcds.getPooledConnection("u1", "p1").getConnection(); - - for (final Connection element : c) { - element.close(); - checkAfterClose(element, null); - } - - // open all the connections - for (int i = 0; i < c.length; i++) { - c[i] = pcds.getPooledConnection("u1", "p1").getConnection(); - } - for (final Connection element : c) { - element.close(); - checkAfterClose(element, null); - } - } - - /** - * Tests https://issues.apache.org/jira/browse/DBCP-376 - */ - @Test - public void testDbcp367() throws Exception { - final ThreadDbcp367[] threads = new ThreadDbcp367[200]; - - pcds.setPoolPreparedStatements(true); - pcds.setMaxPreparedStatements(-1); - pcds.setAccessToUnderlyingConnectionAllowed(true); - - try (final SharedPoolDataSource spds = new SharedPoolDataSource()) { - spds.setConnectionPoolDataSource(pcds); - spds.setMaxTotal(threads.length + 10); - spds.setDefaultMaxWait(Duration.ofMillis(-1)); - spds.setDefaultMaxIdle(10); - spds.setDefaultAutoCommit(Boolean.FALSE); - - spds.setValidationQuery("SELECT 1"); - spds.setDefaultDurationBetweenEvictionRuns(Duration.ofSeconds(10)); - spds.setDefaultNumTestsPerEvictionRun(-1); - spds.setDefaultTestWhileIdle(true); - spds.setDefaultTestOnBorrow(true); - spds.setDefaultTestOnReturn(false); - - for (int i = 0; i < threads.length; i++) { - threads[i] = new ThreadDbcp367(spds); - threads[i].start(); - } - - for (int i = 0; i < threads.length; i++) { - threads[i].join(); - Assertions.assertFalse(threads[i].isFailed(), "Thread " + i + " has failed"); - } - } - } - - @SuppressWarnings("deprecation") - @Test - public void testDeprecatedAccessors() { - int i = 0; - // - i++; - pcds.setMinEvictableIdleTimeMillis(i); - assertEquals(i, pcds.getMinEvictableIdleTimeMillis()); - assertEquals(Duration.ofMillis(i), pcds.getMinEvictableIdleDuration()); - // - i++; - pcds.setTimeBetweenEvictionRunsMillis(i); - assertEquals(i, pcds.getTimeBetweenEvictionRunsMillis()); - assertEquals(Duration.ofMillis(i), pcds.getDurationBetweenEvictionRuns()); - } - - @Test - public void testGetObjectInstance() throws Exception { - final Reference ref = pcds.getReference(); - final Object o = pcds.getObjectInstance(ref, null, null, null); - assertEquals(pcds.getDriver(), ((DriverAdapterCPDS) o).getDriver()); - } - - @Test - public void testGetObjectInstanceChangeDescription() throws Exception { - final Reference ref = pcds.getReference(); - for (int i = 0; i < ref.size(); i++) { - if (ref.get(i).getType().equals("description")) { - ref.remove(i); - break; - } - } - ref.add(new StringRefAddr("description", "anything")); - final Object o = pcds.getObjectInstance(ref, null, null, null); - assertEquals(pcds.getDescription(), ((DriverAdapterCPDS) o).getDescription()); - } - - @Test - public void testGetObjectInstanceNull() throws Exception { - final Object o = pcds.getObjectInstance(null, null, null, null); - assertNull(o); - } - - @Test - public void testGetParentLogger() { - assertThrows(SQLFeatureNotSupportedException.class, pcds::getParentLogger); - } - - @Test - public void testGetReference() throws NamingException { - final Reference ref = pcds.getReference(); - assertEquals(pcds.getDriver(), ref.get("driver").getContent()); - assertEquals(pcds.getDescription(), ref.get("description").getContent()); - } - - @Test - public void testGettersAndSetters() { - pcds.setUser("foo"); - assertEquals("foo", pcds.getUser()); - pcds.setPassword("bar"); - assertEquals("bar", pcds.getPassword()); - pcds.setPassword(new char[] {'a', 'b'}); - assertArrayEquals(new char[] {'a', 'b'}, pcds.getPasswordCharArray()); - final PrintWriter pw = new PrintWriter(System.err); - pcds.setLogWriter(pw); - @SuppressWarnings("resource") - final PrintWriter logWriter = pcds.getLogWriter(); - assertEquals(pw, logWriter); - pcds.setLoginTimeout(10); - assertEquals(10, pcds.getLoginTimeout()); - pcds.setMaxIdle(100); - assertEquals(100, pcds.getMaxIdle()); - pcds.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); - assertEquals(100, pcds.getDurationBetweenEvictionRuns().toMillis()); - pcds.setNumTestsPerEvictionRun(1); - assertEquals(1, pcds.getNumTestsPerEvictionRun()); - pcds.setMinEvictableIdleDuration(Duration.ofMillis(11)); - assertEquals(Duration.ofMillis(11), pcds.getMinEvictableIdleDuration()); - pcds.setDescription("jo"); - assertEquals("jo", pcds.getDescription()); - } - - /** - * JIRA: DBCP-245 - */ - @Test - public void testIncorrectPassword() throws Exception { - pcds.getPooledConnection("u2", "p2").close(); - // Use bad password - assertThrows(SQLException.class, () -> pcds.getPooledConnection("u1", "zlsafjk"), "Able to retrieve connection with incorrect password"); - - // Use good password - final SQLException e = assertThrows(SQLException.class, () -> pcds.getPooledConnection("u1", "x"), "Able to retrieve connection with incorrect password"); - assertTrue(e.getMessage().startsWith("x is not the correct password")); - // else the exception was expected - - // Make sure we can still use our good password. - pcds.getPooledConnection("u1", "p1").close(); - } - - /** - * JIRA: DBCP-442 - */ - @Test - public void testNullValidationQuery() throws Exception { - try (final SharedPoolDataSource spds = new SharedPoolDataSource()) { - spds.setConnectionPoolDataSource(pcds); - spds.setDefaultTestOnBorrow(true); - try (final Connection c = spds.getConnection()) { - // close right away - } - } - } - - @Test - public void testSetConnectionProperties() throws Exception { - // Set user property to bad value - pcds.setUser("bad"); - // Supply correct value in connection properties - // This will overwrite field value - final Properties properties = new Properties(); - properties.put(Constants.KEY_USER, "foo"); - properties.put(Constants.KEY_PASSWORD, pcds.getPassword()); - pcds.setConnectionProperties(properties); - pcds.getPooledConnection().close(); - assertEquals("foo", pcds.getUser()); - // Put bad password into properties - properties.put("password", "bad"); - // This does not change local field - assertEquals("bar", pcds.getPassword()); - // Supply correct password in getPooledConnection - // Call will succeed and overwrite property - pcds.getPooledConnection("foo", "bar").close(); - assertEquals("bar", pcds.getConnectionProperties().getProperty("password")); - } - - @Test - public void testSetConnectionPropertiesConnectionCalled() throws Exception { - final Properties properties = new Properties(); - // call to the connection - pcds.getPooledConnection().close(); - assertThrows(IllegalStateException.class, () -> pcds.setConnectionProperties(properties)); - } - - @Test - public void testSetConnectionPropertiesNull() throws Exception { - pcds.setConnectionProperties(null); - } - - @Test - public void testSetPasswordNull() throws Exception { - pcds.setPassword("Secret"); - assertEquals("Secret", pcds.getPassword()); - pcds.setPassword((char[]) null); - assertNull(pcds.getPassword()); - } - - @Test - public void testSetPasswordNullWithConnectionProperties() throws Exception { - pcds.setConnectionProperties(new Properties()); - pcds.setPassword("Secret"); - assertEquals("Secret", pcds.getPassword()); - pcds.setPassword((char[]) null); - assertNull(pcds.getPassword()); - } - - @Test - public void testSetPasswordThenModCharArray() { - final char[] pwd = {'a'}; - pcds.setPassword(pwd); - assertEquals("a", pcds.getPassword()); - pwd[0] = 'b'; - assertEquals("a", pcds.getPassword()); - } - - @Test - public void testSetUserNull() throws Exception { - pcds.setUser("Alice"); - assertEquals("Alice", pcds.getUser()); - pcds.setUser(null); - assertNull(pcds.getUser()); - } - - @Test - public void testSetUserNullWithConnectionProperties() throws Exception { - pcds.setConnectionProperties(new Properties()); - pcds.setUser("Alice"); - assertEquals("Alice", pcds.getUser()); - pcds.setUser(null); - assertNull(pcds.getUser()); - } - - @Test - public void testSimple() throws Exception { - try (final Connection conn = pcds.getPooledConnection().getConnection()) { - assertNotNull(conn); - try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (final ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - } - } - - @SuppressWarnings("resource") - @Test - public void testSimpleWithUsername() throws Exception { - final Connection connCheck; - PStmtKey pStmtKey; - try (final Connection conn = pcds.getPooledConnection("u1", "p1").getConnection()) { - assertNotNull(conn); - connCheck = conn; - try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - final DelegatingStatement delegatingStatement = (DelegatingStatement) stmt; - final Statement delegateStatement = delegatingStatement.getDelegate(); - pStmtKey = TestUtils.getPStmtKey((PoolablePreparedStatement) delegateStatement); - assertNotNull(pStmtKey); - try (final ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - } - checkAfterClose(connCheck, pStmtKey); - } - - @Test - public void testToStringWithoutConnectionProperties() throws ClassNotFoundException { - final DriverAdapterCPDS cleanCpds = new DriverAdapterCPDS(); - cleanCpds.setDriver("org.apache.commons.dbcp2.TesterDriver"); - cleanCpds.setUrl("jdbc:apache:commons:testdriver"); - cleanCpds.setUser("foo"); - cleanCpds.setPassword("bar"); - cleanCpds.toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2.cpdsadapter; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.time.Duration; +import java.util.Properties; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.Constants; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.DelegatingStatement; +import org.apache.commons.dbcp2.PStmtKey; +import org.apache.commons.dbcp2.PoolablePreparedStatement; +import org.apache.commons.dbcp2.TestUtils; +import org.apache.commons.dbcp2.datasources.SharedPoolDataSource; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for DriverAdapterCPDS + */ +public class TestDriverAdapterCPDS { + + private static final class ThreadDbcp367 extends Thread { + + private final DataSource dataSource; + + private volatile boolean failed; + + public ThreadDbcp367(final DataSource dataSource) { + this.dataSource = dataSource; + } + + public boolean isFailed() { + return failed; + } + + @Override + public void run() { + Connection conn = null; + try { + for (int j = 0; j < 5000; j++) { + conn = dataSource.getConnection(); + conn.close(); + } + } catch (final SQLException sqle) { + failed = true; + sqle.printStackTrace(); + } + } + } + + @SuppressWarnings("resource") + private static void checkAfterClose(final Connection element, final PStmtKey pStmtKey) throws SQLException { + final ConnectionImpl connectionImpl = (ConnectionImpl) element; + assertNull(connectionImpl.getInnermostDelegate()); + assertNotNull(connectionImpl.getInnermostDelegateInternal()); + final PooledConnectionImpl pooledConnectionImpl = connectionImpl.getPooledConnectionImpl(); + assertNotNull(pooledConnectionImpl); + // Simulate released resources, should not throw NPEs + pooledConnectionImpl.destroyObject(pStmtKey, null); + pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(null)); + pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(new DelegatingPreparedStatement(null, null))); + } + + private DriverAdapterCPDS pcds; + + @BeforeEach + public void setUp() throws Exception { + pcds = new DriverAdapterCPDS(); + pcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + pcds.setUrl("jdbc:apache:commons:testdriver"); + pcds.setUser("foo"); + pcds.setPassword("bar"); + pcds.setPoolPreparedStatements(true); + } + + @Test + public void testClose() + throws Exception { + final Connection[] c = new Connection[10]; + for (int i = 0; i < c.length; i++) { + c[i] = pcds.getPooledConnection().getConnection(); + } + + // close one of the connections + c[0].close(); + assertTrue(c[0].isClosed()); + // get a new connection + c[0] = pcds.getPooledConnection().getConnection(); + + for (final Connection element : c) { + element.close(); + checkAfterClose(element, null); + } + + // open all the connections + for (int i = 0; i < c.length; i++) { + c[i] = pcds.getPooledConnection().getConnection(); + } + for (final Connection element : c) { + element.close(); + checkAfterClose(element, null); + } + } + + @Test + public void testCloseWithUserName() + throws Exception { + final Connection[] c = new Connection[10]; + for (int i = 0; i < c.length; i++) { + c[i] = pcds.getPooledConnection("u1", "p1").getConnection(); + } + + // close one of the connections + c[0].close(); + assertTrue(c[0].isClosed()); + // get a new connection + c[0] = pcds.getPooledConnection("u1", "p1").getConnection(); + + for (final Connection element : c) { + element.close(); + checkAfterClose(element, null); + } + + // open all the connections + for (int i = 0; i < c.length; i++) { + c[i] = pcds.getPooledConnection("u1", "p1").getConnection(); + } + for (final Connection element : c) { + element.close(); + checkAfterClose(element, null); + } + } + + /** + * Tests https://issues.apache.org/jira/browse/DBCP-376 + */ + @Test + public void testDbcp367() throws Exception { + final ThreadDbcp367[] threads = new ThreadDbcp367[200]; + + pcds.setPoolPreparedStatements(true); + pcds.setMaxPreparedStatements(-1); + pcds.setAccessToUnderlyingConnectionAllowed(true); + + try (final SharedPoolDataSource spds = new SharedPoolDataSource()) { + spds.setConnectionPoolDataSource(pcds); + spds.setMaxTotal(threads.length + 10); + spds.setDefaultMaxWait(Duration.ofMillis(-1)); + spds.setDefaultMaxIdle(10); + spds.setDefaultAutoCommit(Boolean.FALSE); + + spds.setValidationQuery("SELECT 1"); + spds.setDefaultDurationBetweenEvictionRuns(Duration.ofSeconds(10)); + spds.setDefaultNumTestsPerEvictionRun(-1); + spds.setDefaultTestWhileIdle(true); + spds.setDefaultTestOnBorrow(true); + spds.setDefaultTestOnReturn(false); + + for (int i = 0; i < threads.length; i++) { + threads[i] = new ThreadDbcp367(spds); + threads[i].start(); + } + + for (int i = 0; i < threads.length; i++) { + threads[i].join(); + Assertions.assertFalse(threads[i].isFailed(), "Thread " + i + " has failed"); + } + } + } + + @SuppressWarnings("deprecation") + @Test + public void testDeprecatedAccessors() { + int i = 0; + // + i++; + pcds.setMinEvictableIdleTimeMillis(i); + assertEquals(i, pcds.getMinEvictableIdleTimeMillis()); + assertEquals(Duration.ofMillis(i), pcds.getMinEvictableIdleDuration()); + // + i++; + pcds.setTimeBetweenEvictionRunsMillis(i); + assertEquals(i, pcds.getTimeBetweenEvictionRunsMillis()); + assertEquals(Duration.ofMillis(i), pcds.getDurationBetweenEvictionRuns()); + } + + @Test + public void testGetObjectInstance() throws Exception { + final Reference ref = pcds.getReference(); + final Object o = pcds.getObjectInstance(ref, null, null, null); + assertEquals(pcds.getDriver(), ((DriverAdapterCPDS) o).getDriver()); + } + + @Test + public void testGetObjectInstanceChangeDescription() throws Exception { + final Reference ref = pcds.getReference(); + for (int i = 0; i < ref.size(); i++) { + if (ref.get(i).getType().equals("description")) { + ref.remove(i); + break; + } + } + ref.add(new StringRefAddr("description", "anything")); + final Object o = pcds.getObjectInstance(ref, null, null, null); + assertEquals(pcds.getDescription(), ((DriverAdapterCPDS) o).getDescription()); + } + + @Test + public void testGetObjectInstanceNull() throws Exception { + final Object o = pcds.getObjectInstance(null, null, null, null); + assertNull(o); + } + + @Test + public void testGetParentLogger() { + assertThrows(SQLFeatureNotSupportedException.class, pcds::getParentLogger); + } + + @Test + public void testGetReference() throws NamingException { + final Reference ref = pcds.getReference(); + assertEquals(pcds.getDriver(), ref.get("driver").getContent()); + assertEquals(pcds.getDescription(), ref.get("description").getContent()); + } + + @Test + public void testGettersAndSetters() { + pcds.setUser("foo"); + assertEquals("foo", pcds.getUser()); + pcds.setPassword("bar"); + assertEquals("bar", pcds.getPassword()); + pcds.setPassword(new char[] {'a', 'b'}); + assertArrayEquals(new char[] {'a', 'b'}, pcds.getPasswordCharArray()); + final PrintWriter pw = new PrintWriter(System.err); + pcds.setLogWriter(pw); + @SuppressWarnings("resource") + final PrintWriter logWriter = pcds.getLogWriter(); + assertEquals(pw, logWriter); + pcds.setLoginTimeout(10); + assertEquals(10, pcds.getLoginTimeout()); + pcds.setMaxIdle(100); + assertEquals(100, pcds.getMaxIdle()); + pcds.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); + assertEquals(100, pcds.getDurationBetweenEvictionRuns().toMillis()); + pcds.setNumTestsPerEvictionRun(1); + assertEquals(1, pcds.getNumTestsPerEvictionRun()); + pcds.setMinEvictableIdleDuration(Duration.ofMillis(11)); + assertEquals(Duration.ofMillis(11), pcds.getMinEvictableIdleDuration()); + pcds.setDescription("jo"); + assertEquals("jo", pcds.getDescription()); + } + + /** + * JIRA: DBCP-245 + */ + @Test + public void testIncorrectPassword() throws Exception { + pcds.getPooledConnection("u2", "p2").close(); + // Use bad password + assertThrows(SQLException.class, () -> pcds.getPooledConnection("u1", "zlsafjk"), "Able to retrieve connection with incorrect password"); + + // Use good password + final SQLException e = assertThrows(SQLException.class, () -> pcds.getPooledConnection("u1", "x"), "Able to retrieve connection with incorrect password"); + assertTrue(e.getMessage().startsWith("x is not the correct password")); + // else the exception was expected + + // Make sure we can still use our good password. + pcds.getPooledConnection("u1", "p1").close(); + } + + /** + * JIRA: DBCP-442 + */ + @Test + public void testNullValidationQuery() throws Exception { + try (final SharedPoolDataSource spds = new SharedPoolDataSource()) { + spds.setConnectionPoolDataSource(pcds); + spds.setDefaultTestOnBorrow(true); + try (final Connection c = spds.getConnection()) { + // close right away + } + } + } + + @Test + public void testSetConnectionProperties() throws Exception { + // Set user property to bad value + pcds.setUser("bad"); + // Supply correct value in connection properties + // This will overwrite field value + final Properties properties = new Properties(); + properties.put(Constants.KEY_USER, "foo"); + properties.put(Constants.KEY_PASSWORD, pcds.getPassword()); + pcds.setConnectionProperties(properties); + pcds.getPooledConnection().close(); + assertEquals("foo", pcds.getUser()); + // Put bad password into properties + properties.put("password", "bad"); + // This does not change local field + assertEquals("bar", pcds.getPassword()); + // Supply correct password in getPooledConnection + // Call will succeed and overwrite property + pcds.getPooledConnection("foo", "bar").close(); + assertEquals("bar", pcds.getConnectionProperties().getProperty("password")); + } + + @Test + public void testSetConnectionPropertiesConnectionCalled() throws Exception { + final Properties properties = new Properties(); + // call to the connection + pcds.getPooledConnection().close(); + assertThrows(IllegalStateException.class, () -> pcds.setConnectionProperties(properties)); + } + + @Test + public void testSetConnectionPropertiesNull() throws Exception { + pcds.setConnectionProperties(null); + } + + @Test + public void testSetPasswordNull() throws Exception { + pcds.setPassword("Secret"); + assertEquals("Secret", pcds.getPassword()); + pcds.setPassword((char[]) null); + assertNull(pcds.getPassword()); + } + + @Test + public void testSetPasswordNullWithConnectionProperties() throws Exception { + pcds.setConnectionProperties(new Properties()); + pcds.setPassword("Secret"); + assertEquals("Secret", pcds.getPassword()); + pcds.setPassword((char[]) null); + assertNull(pcds.getPassword()); + } + + @Test + public void testSetPasswordThenModCharArray() { + final char[] pwd = {'a'}; + pcds.setPassword(pwd); + assertEquals("a", pcds.getPassword()); + pwd[0] = 'b'; + assertEquals("a", pcds.getPassword()); + } + + @Test + public void testSetUserNull() throws Exception { + pcds.setUser("Alice"); + assertEquals("Alice", pcds.getUser()); + pcds.setUser(null); + assertNull(pcds.getUser()); + } + + @Test + public void testSetUserNullWithConnectionProperties() throws Exception { + pcds.setConnectionProperties(new Properties()); + pcds.setUser("Alice"); + assertEquals("Alice", pcds.getUser()); + pcds.setUser(null); + assertNull(pcds.getUser()); + } + + @Test + public void testSimple() throws Exception { + try (final Connection conn = pcds.getPooledConnection().getConnection()) { + assertNotNull(conn); + try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (final ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + } + } + + @SuppressWarnings("resource") + @Test + public void testSimpleWithUsername() throws Exception { + final Connection connCheck; + PStmtKey pStmtKey; + try (final Connection conn = pcds.getPooledConnection("u1", "p1").getConnection()) { + assertNotNull(conn); + connCheck = conn; + try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + final DelegatingStatement delegatingStatement = (DelegatingStatement) stmt; + final Statement delegateStatement = delegatingStatement.getDelegate(); + pStmtKey = TestUtils.getPStmtKey((PoolablePreparedStatement) delegateStatement); + assertNotNull(pStmtKey); + try (final ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + } + checkAfterClose(connCheck, pStmtKey); + } + + @Test + public void testToStringWithoutConnectionProperties() throws ClassNotFoundException { + final DriverAdapterCPDS cleanCpds = new DriverAdapterCPDS(); + cleanCpds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + cleanCpds.setUrl("jdbc:apache:commons:testdriver"); + cleanCpds.setUser("foo"); + cleanCpds.setPassword("bar"); + cleanCpds.toString(); + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java b/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java index eaf64e67b6..b458ca4a68 100644 --- a/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java @@ -1,727 +1,727 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2.datasources; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.time.Duration; -import java.util.ArrayList; -import java.util.NoSuchElementException; - -import javax.sql.DataSource; - -import org.apache.commons.dbcp2.DelegatingStatement; -import org.apache.commons.dbcp2.TestConnectionPool; -import org.apache.commons.dbcp2.TesterDriver; -import org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS; -import org.apache.commons.lang3.ArrayUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - */ -public class TestSharedPoolDataSource extends TestConnectionPool { - - /** - * There are 3 different prepareCall statement methods so add a little complexity to reduce what would otherwise be lots - * of copy and paste. - */ - private static abstract class AbstractPrepareCallCallback { - protected Connection conn; - - abstract CallableStatement getCallableStatement() throws SQLException; - - void setConnection(final Connection conn) { - this.conn = conn; - } - } - - /** - * There are 6 different prepareStatement statement methods so add a little complexity to reduce what would otherwise be - * lots of copy and paste. - */ - private static abstract class AbstractPrepareStatementCallback { - protected Connection conn; - - abstract PreparedStatement prepareStatement() throws SQLException; - - void setConnection(final Connection conn) { - this.conn = conn; - } - } - - private static final class CscbString extends AbstractPrepareCallCallback { - @Override - CallableStatement getCallableStatement() throws SQLException { - return conn.prepareCall("{call home()}"); - } - } - - private static final class CscbStringIntInt extends AbstractPrepareCallCallback { - @Override - CallableStatement getCallableStatement() throws SQLException { - return conn.prepareCall("{call home()}", 0, 0); - } - } - - private static final class CscbStringIntIntInt extends AbstractPrepareCallCallback { - @Override - CallableStatement getCallableStatement() throws SQLException { - return conn.prepareCall("{call home()}", 0, 0, 0); - } - } - - private static final class PscbString extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual"); - } - } - - private static final class PscbStringInt extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual", 0); - } - } - - private static final class PscbStringIntArray extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual", ArrayUtils.EMPTY_INT_ARRAY); - } - } - - private static final class PscbStringIntInt extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual", 0, 0); - } - } - - private static final class PscbStringIntIntInt extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual", 0, 0, 0); - } - } - - private static final class PscbStringStringArray extends AbstractPrepareStatementCallback { - @Override - PreparedStatement prepareStatement() throws SQLException { - return conn.prepareStatement("select * from dual", ArrayUtils.EMPTY_STRING_ARRAY); - } - } - - private DriverAdapterCPDS pcds; - - private DataSource ds; - - private void doTestPoolCallableStatements(final AbstractPrepareCallCallback callBack) - throws Exception { - final DriverAdapterCPDS myPcds = new DriverAdapterCPDS(); - myPcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); - myPcds.setUrl("jdbc:apache:commons:testdriver"); - myPcds.setUser("foo"); - myPcds.setPassword("bar"); - myPcds.setPoolPreparedStatements(true); - myPcds.setMaxPreparedStatements(10); - - try (final SharedPoolDataSource spDs = new SharedPoolDataSource()) { - spDs.setConnectionPoolDataSource(myPcds); - spDs.setMaxTotal(getMaxTotal()); - spDs.setDefaultMaxWait(getMaxWaitDuration()); - spDs.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - - @SuppressWarnings("resource") - final DataSource myDs = spDs; - - try (Connection conn = ds.getConnection()) { - callBack.setConnection(conn); - - assertNotNull(conn); - final long l1HashCode; - final long l2HashCode; - try (CallableStatement stmt = callBack.getCallableStatement()) { - assertNotNull(stmt); - l1HashCode = getDelegateHashCode(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - try (CallableStatement stmt = callBack.getCallableStatement()) { - assertNotNull(stmt); - l2HashCode = getDelegateHashCode(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - // statement pooling is not enabled, we should get different statements - assertTrue(l1HashCode != l2HashCode); - } - - try (Connection conn = myDs.getConnection()) { - callBack.setConnection(conn); - - final long l3HashCode; - final long l4HashCode; - try (CallableStatement stmt = callBack.getCallableStatement()) { - assertNotNull(stmt); - l3HashCode = getDelegateHashCode(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - try (CallableStatement stmt = callBack.getCallableStatement()) { - assertNotNull(stmt); - l4HashCode = getDelegateHashCode(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - // prepared statement pooling is working - assertEquals(l3HashCode, l4HashCode); - } - } - } - - private void doTestPoolPreparedStatements(final AbstractPrepareStatementCallback psCallBack) throws Exception { - final DriverAdapterCPDS mypcds = new DriverAdapterCPDS(); - mypcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); - mypcds.setUrl("jdbc:apache:commons:testdriver"); - mypcds.setUser("foo"); - mypcds.setPassword("bar"); - mypcds.setPoolPreparedStatements(true); - mypcds.setMaxPreparedStatements(10); - - try (final SharedPoolDataSource tds = new SharedPoolDataSource()) { - tds.setConnectionPoolDataSource(mypcds); - tds.setMaxTotal(getMaxTotal()); - tds.setDefaultMaxWait(getMaxWaitDuration()); - tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - - @SuppressWarnings("resource") - final - DataSource myDs = tds; - - try (Connection conn = ds.getConnection()) { - final long l1HashCode; - final long l2HashCode; - assertNotNull(conn); - psCallBack.setConnection(conn); - try (PreparedStatement stmt = psCallBack.prepareStatement()) { - assertNotNull(stmt); - l1HashCode = getDelegateHashCode(stmt); - try (ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - - try (PreparedStatement stmt = psCallBack.prepareStatement()) { - assertNotNull(stmt); - l2HashCode = getDelegateHashCode(stmt); - try (ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - - // statement pooling is not enabled, we should get different statements - assertTrue(l1HashCode != l2HashCode); - } - - try (Connection conn = myDs.getConnection()) { - final long l3HashCode; - final long l4HashCode; - - assertNotNull(conn); - psCallBack.setConnection(conn); - try (PreparedStatement stmt = psCallBack.prepareStatement()) { - assertNotNull(stmt); - l3HashCode = getDelegateHashCode(stmt); - try (ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - - try (PreparedStatement stmt = psCallBack.prepareStatement()) { - assertNotNull(stmt); - l4HashCode = getDelegateHashCode(stmt); - try (ResultSet resultSet = stmt.executeQuery()) { - assertNotNull(resultSet); - assertTrue(resultSet.next()); - } - } - - // prepared statement pooling is working - assertEquals(l3HashCode, l4HashCode); - } - } - } - - @Override - protected Connection getConnection() throws Exception { - return ds.getConnection("foo", "bar"); - } - - @SuppressWarnings("resource") - private int getDelegateHashCode(final Statement stmt) { - return ((DelegatingStatement) stmt).getDelegate().hashCode(); - } - - @BeforeEach - public void setUp() throws Exception { - pcds = new DriverAdapterCPDS(); - pcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); - pcds.setUrl("jdbc:apache:commons:testdriver"); - pcds.setUser("foo"); - pcds.setPassword("bar"); - pcds.setPoolPreparedStatements(false); - pcds.setAccessToUnderlyingConnectionAllowed(true); - - final SharedPoolDataSource tds = new SharedPoolDataSource(); - tds.setConnectionPoolDataSource(pcds); - tds.setMaxTotal(getMaxTotal()); - tds.setDefaultMaxWait(getMaxWaitDuration()); - tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - tds.setDefaultAutoCommit(Boolean.TRUE); - - ds = tds; - } - - // See DBCP-8 - @Test - public void testChangePassword() throws Exception { - assertThrows(SQLException.class, () -> ds.getConnection("foo", "bay")); - final Connection con1 = ds.getConnection("foo", "bar"); - final Connection con2 = ds.getConnection("foo", "bar"); - final Connection con3 = ds.getConnection("foo", "bar"); - con1.close(); - con2.close(); - TesterDriver.addUser("foo", "bay"); // change the user/password setting - try (Connection con4 = ds.getConnection("foo", "bay")) { // new password - // Idle instances with old password should have been cleared - assertEquals(0, ((SharedPoolDataSource) ds).getNumIdle(), "Should be no idle connections in the pool"); - con4.close(); - // Should be one idle instance with new pwd - assertEquals(1, ((SharedPoolDataSource) ds).getNumIdle(), "Should be one idle connection in the pool"); - assertThrows(SQLException.class, () -> ds.getConnection("foo", "bar")); // old password - try (final Connection con5 = ds.getConnection("foo", "bay")) { // take the idle one - con3.close(); // Return a connection with the old password - ds.getConnection("foo", "bay").close(); // will try bad returned connection and destroy it - assertEquals(1, ((SharedPoolDataSource) ds).getNumIdle(), "Should be one idle connection in the pool"); - } - } finally { - TesterDriver.addUser("foo", "bar"); - } - } - - /** - * Tests pool close. Illustrates BZ 37359. - * - * @throws Exception - */ - @Test - public void testClosePool() throws Exception { - ((SharedPoolDataSource) ds).close(); - @SuppressWarnings("resource") // closed below - final SharedPoolDataSource tds = new SharedPoolDataSource(); - // NPE before BZ 37359 fix - tds.close(); - } - - @Override - @Test - public void testClosing() throws Exception { - final Connection[] c = new Connection[getMaxTotal()]; - // open the maximum connections - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection(); - } - - // close one of the connections - c[0].close(); - assertTrue(c[0].isClosed()); - - // get a new connection - c[0] = ds.getConnection(); - - for (final Connection element : c) { - element.close(); - } - } - - @Test - public void testClosingWithUserName() throws Exception { - final Connection[] c = new Connection[getMaxTotal()]; - // open the maximum connections - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection("u1", "p1"); - } - - // close one of the connections - c[0].close(); - assertTrue(c[0].isClosed()); - // get a new connection - c[0] = ds.getConnection("u1", "p1"); - - for (final Connection element : c) { - element.close(); - } - - // open the maximum connections - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection("u1", "p1"); - } - for (final Connection element : c) { - element.close(); - } - } - - @Test - public void testDbcp369() { - final ArrayList dataSources = new ArrayList<>(); - for (int j = 0; j < 10000; j++) { - dataSources.add(new SharedPoolDataSource()); - } - - final Thread t1 = new Thread(() -> { - for (final SharedPoolDataSource dataSource : dataSources) { - dataSource.setDataSourceName("a"); - } - }); - - final Thread t2 = new Thread(() -> { - for (final SharedPoolDataSource dataSource : dataSources) { - try { - dataSource.close(); - } catch (final Exception e) { - // Ignore - } - } - }); - - t1.start(); - t2.start(); - - try { - t1.join(); - t2.join(); - } catch (final InterruptedException ie) { - // Ignore - } - } - - /** - * Tests https://issues.apache.org/jira/browse/DBCP-597 - */ - @Test - public void testDbcp597() throws SQLException { - try (final SharedPoolDataSource sharedPoolDataSource = new SharedPoolDataSource()) { - sharedPoolDataSource.setConnectionPoolDataSource(pcds); - sharedPoolDataSource.setDefaultTestOnBorrow(true); - sharedPoolDataSource.setValidationQuery("SELECT 1"); - // The tester statement throws a SQLTimeoutException when the timeout is > 0 and < 5. - sharedPoolDataSource.setValidationQueryTimeout(Duration.ofSeconds(1)); - // The SQLTimeoutException is lost for now - SQLException e = assertThrows(SQLException.class, sharedPoolDataSource::getConnection); - assertEquals(NoSuchElementException.class, e.getCause().getClass()); - // timeout > 0 and < 1 - sharedPoolDataSource.setValidationQueryTimeout(Duration.ofMillis(999)); - // The SQLTimeoutException is lost for now - e = assertThrows(SQLException.class, sharedPoolDataSource::getConnection); - assertEquals(NoSuchElementException.class, e.getCause().getClass()); - } - } - - /** - * Starting with a successful connection, then incorrect password, then correct password for same user illustrates JIRA: DBCP-245 - */ - @Test - public void testIncorrectPassword() throws SQLException { - ds.getConnection("u2", "p2").close(); - assertThrows(SQLException.class, () -> ds.getConnection("u1", "zlsafjk"), "Able to retrieve connection with incorrect password"); - // Use good password - ds.getConnection("u1", "p1").close(); - final SQLException e = assertThrows(SQLException.class, () -> ds.getConnection("u1", "x"), "Able to retrieve connection with incorrect password"); - assertTrue(e.getMessage().startsWith("Given password did not match")); - // Make sure we can still use our good password. - ds.getConnection("u1", "p1").close(); - // Try related users and passwords - ds.getConnection("foo", "bar").close(); - assertThrows(SQLException.class, () -> ds.getConnection("u1", "ar")); - assertThrows(SQLException.class, () -> ds.getConnection("u1", "baz")); - } - - @Override - @Test - public void testMaxTotal() throws Exception { - final Connection[] c = new Connection[getMaxTotal()]; - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection(); - assertNotNull(c[i]); - } - assertThrows(SQLException.class, ds::getConnection, "Allowed to open more than DefaultMaxTotal connections."); - for (final Connection element : c) { - element.close(); - } - } - - @Test - public void testMaxWaitMillis() throws Exception { - final int maxWaitMillis = 1000; - final int theadCount = 20; - - ((SharedPoolDataSource) ds).setDefaultMaxWait(Duration.ofMillis(maxWaitMillis)); - // Obtain all the connections from the pool - final Connection[] c = new Connection[getMaxTotal()]; - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection("foo", "bar"); - assertNotNull(c[i]); - } - - final long startMillis = System.currentTimeMillis(); - - // Run a thread test with minimal hold time - // All threads should end after maxWaitMillis - DBCP-291 - final PoolTest[] pts = new PoolTest[theadCount]; - final ThreadGroup threadGroup = new ThreadGroup("testMaxWaitMillis"); - - // Should take ~maxWaitMillis for threads to stop - for (int i = 0; i < pts.length; i++) { - pts[i] = new PoolTest(threadGroup, Duration.ofMillis(1), true); - pts[i].start(); - } - - // Wait for all the threads to complete - for (final PoolTest poolTest : pts) { - poolTest.getThread().join(); - } - - final long endMillis = System.currentTimeMillis(); - - // System.out.println("testMaxWaitMillis took " + (end - start) + " ms. maxWaitMillis: " + maxWaitMillis); - - // Threads should time out in parallel - allow double that to be safe - assertTrue(endMillis - startMillis < 2 * maxWaitMillis); - - // Put all the connections back in the pool - for (final Connection element : c) { - element.close(); - } - } - - @Test - public void testMultipleThreads1() throws Exception { - // Override wait time in order to allow for Thread.sleep(1) sometimes taking a lot longer on - // some JVMs, e.g. Windows. - final Duration defaultMaxWaitDuration = Duration.ofMillis(430); - ((SharedPoolDataSource) ds).setDefaultMaxWait(defaultMaxWaitDuration); - multipleThreads(Duration.ofMillis(1), false, false, defaultMaxWaitDuration); - } - - @Test - public void testMultipleThreads2() throws Exception { - final Duration defaultMaxWaitDuration = Duration.ofMillis(500); - ((SharedPoolDataSource) ds).setDefaultMaxWait(defaultMaxWaitDuration); - multipleThreads(defaultMaxWaitDuration.multipliedBy(2), true, true, defaultMaxWaitDuration); - } - - @Override - @Test - public void testOpening() throws Exception { - final Connection[] c = new Connection[getMaxTotal()]; - // test that opening new connections is not closing previous - for (int i = 0; i < c.length; i++) { - c[i] = ds.getConnection(); - assertNotNull(c[i]); - for (int j = 0; j <= i; j++) { - assertFalse(c[j].isClosed()); - } - } - - for (final Connection element : c) { - element.close(); - } - } - - /** - * Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS when setPoolPreparedStatements(true) - */ - @Test - public void testPoolPrepareCall() throws SQLException { - pcds.setPoolPreparedStatements(true); - try (final Connection conn = ds.getConnection()) { - assertNotNull(conn); - try (final PreparedStatement stmt = conn.prepareCall("{call home()}")) { - assertNotNull(stmt); - try (final ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - } - } - - @Test - public void testPoolPreparedCalls() throws Exception { - doTestPoolCallableStatements(new CscbString()); - doTestPoolCallableStatements(new CscbStringIntInt()); - doTestPoolCallableStatements(new CscbStringIntIntInt()); - } - - @Test - public void testPoolPreparedStatements() throws Exception { - doTestPoolPreparedStatements(new PscbString()); - doTestPoolPreparedStatements(new PscbStringIntInt()); - doTestPoolPreparedStatements(new PscbStringInt()); - doTestPoolPreparedStatements(new PscbStringIntArray()); - doTestPoolPreparedStatements(new PscbStringStringArray()); - doTestPoolPreparedStatements(new PscbStringIntIntInt()); - } - - @Test - public void testPoolPrepareStatement() throws SQLException { - pcds.setPoolPreparedStatements(true); - - try (final Connection conn = ds.getConnection()) { - assertNotNull(conn); - try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (final ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - } - } - - @Override - @Test - public void testSimple() throws Exception { - try (final Connection conn = ds.getConnection()) { - assertNotNull(conn); - try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (final ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - } - } - - @Override - @Test - public void testSimple2() throws SQLException { - { - final Connection conn = ds.getConnection(); - assertNotNull(conn); - - try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - conn.close(); - assertThrows(SQLException.class, () -> conn.createStatement(), "Can't use closed connections"); - } - try (Connection conn = ds.getConnection()) { - assertNotNull(conn); - - try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - - } - } - - @Test - public void testSimpleWithUsername() throws Exception { - try (final Connection conn = ds.getConnection("u1", "p1")) { - assertNotNull(conn); - try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { - assertNotNull(stmt); - try (final ResultSet rset = stmt.executeQuery()) { - assertNotNull(rset); - assertTrue(rset.next()); - } - } - } - } - - @Test - public void testTransactionIsolationBehavior() throws Exception { - try (final Connection conn = getConnection()) { - assertNotNull(conn); - assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation()); - conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); - } - - final Connection conn2 = getConnection(); - assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn2.getTransactionIsolation()); - - final Connection conn3 = getConnection(); - assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn3.getTransactionIsolation()); - conn2.close(); - conn3.close(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2.datasources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Duration; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.DelegatingStatement; +import org.apache.commons.dbcp2.TestConnectionPool; +import org.apache.commons.dbcp2.TesterDriver; +import org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + */ +public class TestSharedPoolDataSource extends TestConnectionPool { + + /** + * There are 3 different prepareCall statement methods so add a little complexity to reduce what would otherwise be lots + * of copy and paste. + */ + private static abstract class AbstractPrepareCallCallback { + protected Connection conn; + + abstract CallableStatement getCallableStatement() throws SQLException; + + void setConnection(final Connection conn) { + this.conn = conn; + } + } + + /** + * There are 6 different prepareStatement statement methods so add a little complexity to reduce what would otherwise be + * lots of copy and paste. + */ + private static abstract class AbstractPrepareStatementCallback { + protected Connection conn; + + abstract PreparedStatement prepareStatement() throws SQLException; + + void setConnection(final Connection conn) { + this.conn = conn; + } + } + + private static final class CscbString extends AbstractPrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}"); + } + } + + private static final class CscbStringIntInt extends AbstractPrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}", 0, 0); + } + } + + private static final class CscbStringIntIntInt extends AbstractPrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}", 0, 0, 0); + } + } + + private static final class PscbString extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual"); + } + } + + private static final class PscbStringInt extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual", 0); + } + } + + private static final class PscbStringIntArray extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual", ArrayUtils.EMPTY_INT_ARRAY); + } + } + + private static final class PscbStringIntInt extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual", 0, 0); + } + } + + private static final class PscbStringIntIntInt extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual", 0, 0, 0); + } + } + + private static final class PscbStringStringArray extends AbstractPrepareStatementCallback { + @Override + PreparedStatement prepareStatement() throws SQLException { + return conn.prepareStatement("select * from dual", ArrayUtils.EMPTY_STRING_ARRAY); + } + } + + private DriverAdapterCPDS pcds; + + private DataSource ds; + + private void doTestPoolCallableStatements(final AbstractPrepareCallCallback callBack) + throws Exception { + final DriverAdapterCPDS myPcds = new DriverAdapterCPDS(); + myPcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + myPcds.setUrl("jdbc:apache:commons:testdriver"); + myPcds.setUser("foo"); + myPcds.setPassword("bar"); + myPcds.setPoolPreparedStatements(true); + myPcds.setMaxPreparedStatements(10); + + try (final SharedPoolDataSource spDs = new SharedPoolDataSource()) { + spDs.setConnectionPoolDataSource(myPcds); + spDs.setMaxTotal(getMaxTotal()); + spDs.setDefaultMaxWait(getMaxWaitDuration()); + spDs.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + + @SuppressWarnings("resource") + final DataSource myDs = spDs; + + try (Connection conn = ds.getConnection()) { + callBack.setConnection(conn); + + assertNotNull(conn); + final long l1HashCode; + final long l2HashCode; + try (CallableStatement stmt = callBack.getCallableStatement()) { + assertNotNull(stmt); + l1HashCode = getDelegateHashCode(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + try (CallableStatement stmt = callBack.getCallableStatement()) { + assertNotNull(stmt); + l2HashCode = getDelegateHashCode(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + // statement pooling is not enabled, we should get different statements + assertTrue(l1HashCode != l2HashCode); + } + + try (Connection conn = myDs.getConnection()) { + callBack.setConnection(conn); + + final long l3HashCode; + final long l4HashCode; + try (CallableStatement stmt = callBack.getCallableStatement()) { + assertNotNull(stmt); + l3HashCode = getDelegateHashCode(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + try (CallableStatement stmt = callBack.getCallableStatement()) { + assertNotNull(stmt); + l4HashCode = getDelegateHashCode(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + // prepared statement pooling is working + assertEquals(l3HashCode, l4HashCode); + } + } + } + + private void doTestPoolPreparedStatements(final AbstractPrepareStatementCallback psCallBack) throws Exception { + final DriverAdapterCPDS mypcds = new DriverAdapterCPDS(); + mypcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + mypcds.setUrl("jdbc:apache:commons:testdriver"); + mypcds.setUser("foo"); + mypcds.setPassword("bar"); + mypcds.setPoolPreparedStatements(true); + mypcds.setMaxPreparedStatements(10); + + try (final SharedPoolDataSource tds = new SharedPoolDataSource()) { + tds.setConnectionPoolDataSource(mypcds); + tds.setMaxTotal(getMaxTotal()); + tds.setDefaultMaxWait(getMaxWaitDuration()); + tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + + @SuppressWarnings("resource") + final + DataSource myDs = tds; + + try (Connection conn = ds.getConnection()) { + final long l1HashCode; + final long l2HashCode; + assertNotNull(conn); + psCallBack.setConnection(conn); + try (PreparedStatement stmt = psCallBack.prepareStatement()) { + assertNotNull(stmt); + l1HashCode = getDelegateHashCode(stmt); + try (ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + + try (PreparedStatement stmt = psCallBack.prepareStatement()) { + assertNotNull(stmt); + l2HashCode = getDelegateHashCode(stmt); + try (ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + + // statement pooling is not enabled, we should get different statements + assertTrue(l1HashCode != l2HashCode); + } + + try (Connection conn = myDs.getConnection()) { + final long l3HashCode; + final long l4HashCode; + + assertNotNull(conn); + psCallBack.setConnection(conn); + try (PreparedStatement stmt = psCallBack.prepareStatement()) { + assertNotNull(stmt); + l3HashCode = getDelegateHashCode(stmt); + try (ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + + try (PreparedStatement stmt = psCallBack.prepareStatement()) { + assertNotNull(stmt); + l4HashCode = getDelegateHashCode(stmt); + try (ResultSet resultSet = stmt.executeQuery()) { + assertNotNull(resultSet); + assertTrue(resultSet.next()); + } + } + + // prepared statement pooling is working + assertEquals(l3HashCode, l4HashCode); + } + } + } + + @Override + protected Connection getConnection() throws Exception { + return ds.getConnection("foo", "bar"); + } + + @SuppressWarnings("resource") + private int getDelegateHashCode(final Statement stmt) { + return ((DelegatingStatement) stmt).getDelegate().hashCode(); + } + + @BeforeEach + public void setUp() throws Exception { + pcds = new DriverAdapterCPDS(); + pcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + pcds.setUrl("jdbc:apache:commons:testdriver"); + pcds.setUser("foo"); + pcds.setPassword("bar"); + pcds.setPoolPreparedStatements(false); + pcds.setAccessToUnderlyingConnectionAllowed(true); + + final SharedPoolDataSource tds = new SharedPoolDataSource(); + tds.setConnectionPoolDataSource(pcds); + tds.setMaxTotal(getMaxTotal()); + tds.setDefaultMaxWait(getMaxWaitDuration()); + tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + tds.setDefaultAutoCommit(Boolean.TRUE); + + ds = tds; + } + + // See DBCP-8 + @Test + public void testChangePassword() throws Exception { + assertThrows(SQLException.class, () -> ds.getConnection("foo", "bay")); + final Connection con1 = ds.getConnection("foo", "bar"); + final Connection con2 = ds.getConnection("foo", "bar"); + final Connection con3 = ds.getConnection("foo", "bar"); + con1.close(); + con2.close(); + TesterDriver.addUser("foo", "bay"); // change the user/password setting + try (Connection con4 = ds.getConnection("foo", "bay")) { // new password + // Idle instances with old password should have been cleared + assertEquals(0, ((SharedPoolDataSource) ds).getNumIdle(), "Should be no idle connections in the pool"); + con4.close(); + // Should be one idle instance with new pwd + assertEquals(1, ((SharedPoolDataSource) ds).getNumIdle(), "Should be one idle connection in the pool"); + assertThrows(SQLException.class, () -> ds.getConnection("foo", "bar")); // old password + try (final Connection con5 = ds.getConnection("foo", "bay")) { // take the idle one + con3.close(); // Return a connection with the old password + ds.getConnection("foo", "bay").close(); // will try bad returned connection and destroy it + assertEquals(1, ((SharedPoolDataSource) ds).getNumIdle(), "Should be one idle connection in the pool"); + } + } finally { + TesterDriver.addUser("foo", "bar"); + } + } + + /** + * Tests pool close. Illustrates BZ 37359. + * + * @throws Exception + */ + @Test + public void testClosePool() throws Exception { + ((SharedPoolDataSource) ds).close(); + @SuppressWarnings("resource") // closed below + final SharedPoolDataSource tds = new SharedPoolDataSource(); + // NPE before BZ 37359 fix + tds.close(); + } + + @Override + @Test + public void testClosing() throws Exception { + final Connection[] c = new Connection[getMaxTotal()]; + // open the maximum connections + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection(); + } + + // close one of the connections + c[0].close(); + assertTrue(c[0].isClosed()); + + // get a new connection + c[0] = ds.getConnection(); + + for (final Connection element : c) { + element.close(); + } + } + + @Test + public void testClosingWithUserName() throws Exception { + final Connection[] c = new Connection[getMaxTotal()]; + // open the maximum connections + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection("u1", "p1"); + } + + // close one of the connections + c[0].close(); + assertTrue(c[0].isClosed()); + // get a new connection + c[0] = ds.getConnection("u1", "p1"); + + for (final Connection element : c) { + element.close(); + } + + // open the maximum connections + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection("u1", "p1"); + } + for (final Connection element : c) { + element.close(); + } + } + + @Test + public void testDbcp369() { + final ArrayList dataSources = new ArrayList<>(); + for (int j = 0; j < 10000; j++) { + dataSources.add(new SharedPoolDataSource()); + } + + final Thread t1 = new Thread(() -> { + for (final SharedPoolDataSource dataSource : dataSources) { + dataSource.setDataSourceName("a"); + } + }); + + final Thread t2 = new Thread(() -> { + for (final SharedPoolDataSource dataSource : dataSources) { + try { + dataSource.close(); + } catch (final Exception e) { + // Ignore + } + } + }); + + t1.start(); + t2.start(); + + try { + t1.join(); + t2.join(); + } catch (final InterruptedException ie) { + // Ignore + } + } + + /** + * Tests https://issues.apache.org/jira/browse/DBCP-597 + */ + @Test + public void testDbcp597() throws SQLException { + try (final SharedPoolDataSource sharedPoolDataSource = new SharedPoolDataSource()) { + sharedPoolDataSource.setConnectionPoolDataSource(pcds); + sharedPoolDataSource.setDefaultTestOnBorrow(true); + sharedPoolDataSource.setValidationQuery("SELECT 1"); + // The tester statement throws a SQLTimeoutException when the timeout is > 0 and < 5. + sharedPoolDataSource.setValidationQueryTimeout(Duration.ofSeconds(1)); + // The SQLTimeoutException is lost for now + SQLException e = assertThrows(SQLException.class, sharedPoolDataSource::getConnection); + assertEquals(NoSuchElementException.class, e.getCause().getClass()); + // timeout > 0 and < 1 + sharedPoolDataSource.setValidationQueryTimeout(Duration.ofMillis(999)); + // The SQLTimeoutException is lost for now + e = assertThrows(SQLException.class, sharedPoolDataSource::getConnection); + assertEquals(NoSuchElementException.class, e.getCause().getClass()); + } + } + + /** + * Starting with a successful connection, then incorrect password, then correct password for same user illustrates JIRA: DBCP-245 + */ + @Test + public void testIncorrectPassword() throws SQLException { + ds.getConnection("u2", "p2").close(); + assertThrows(SQLException.class, () -> ds.getConnection("u1", "zlsafjk"), "Able to retrieve connection with incorrect password"); + // Use good password + ds.getConnection("u1", "p1").close(); + final SQLException e = assertThrows(SQLException.class, () -> ds.getConnection("u1", "x"), "Able to retrieve connection with incorrect password"); + assertTrue(e.getMessage().startsWith("Given password did not match")); + // Make sure we can still use our good password. + ds.getConnection("u1", "p1").close(); + // Try related users and passwords + ds.getConnection("foo", "bar").close(); + assertThrows(SQLException.class, () -> ds.getConnection("u1", "ar")); + assertThrows(SQLException.class, () -> ds.getConnection("u1", "baz")); + } + + @Override + @Test + public void testMaxTotal() throws Exception { + final Connection[] c = new Connection[getMaxTotal()]; + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection(); + assertNotNull(c[i]); + } + assertThrows(SQLException.class, ds::getConnection, "Allowed to open more than DefaultMaxTotal connections."); + for (final Connection element : c) { + element.close(); + } + } + + @Test + public void testMaxWaitMillis() throws Exception { + final int maxWaitMillis = 1000; + final int theadCount = 20; + + ((SharedPoolDataSource) ds).setDefaultMaxWait(Duration.ofMillis(maxWaitMillis)); + // Obtain all the connections from the pool + final Connection[] c = new Connection[getMaxTotal()]; + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection("foo", "bar"); + assertNotNull(c[i]); + } + + final long startMillis = System.currentTimeMillis(); + + // Run a thread test with minimal hold time + // All threads should end after maxWaitMillis - DBCP-291 + final PoolTest[] pts = new PoolTest[theadCount]; + final ThreadGroup threadGroup = new ThreadGroup("testMaxWaitMillis"); + + // Should take ~maxWaitMillis for threads to stop + for (int i = 0; i < pts.length; i++) { + pts[i] = new PoolTest(threadGroup, Duration.ofMillis(1), true); + pts[i].start(); + } + + // Wait for all the threads to complete + for (final PoolTest poolTest : pts) { + poolTest.getThread().join(); + } + + final long endMillis = System.currentTimeMillis(); + + // System.out.println("testMaxWaitMillis took " + (end - start) + " ms. maxWaitMillis: " + maxWaitMillis); + + // Threads should time out in parallel - allow double that to be safe + assertTrue(endMillis - startMillis < 2 * maxWaitMillis); + + // Put all the connections back in the pool + for (final Connection element : c) { + element.close(); + } + } + + @Test + public void testMultipleThreads1() throws Exception { + // Override wait time in order to allow for Thread.sleep(1) sometimes taking a lot longer on + // some JVMs, e.g. Windows. + final Duration defaultMaxWaitDuration = Duration.ofMillis(430); + ((SharedPoolDataSource) ds).setDefaultMaxWait(defaultMaxWaitDuration); + multipleThreads(Duration.ofMillis(1), false, false, defaultMaxWaitDuration); + } + + @Test + public void testMultipleThreads2() throws Exception { + final Duration defaultMaxWaitDuration = Duration.ofMillis(500); + ((SharedPoolDataSource) ds).setDefaultMaxWait(defaultMaxWaitDuration); + multipleThreads(defaultMaxWaitDuration.multipliedBy(2), true, true, defaultMaxWaitDuration); + } + + @Override + @Test + public void testOpening() throws Exception { + final Connection[] c = new Connection[getMaxTotal()]; + // test that opening new connections is not closing previous + for (int i = 0; i < c.length; i++) { + c[i] = ds.getConnection(); + assertNotNull(c[i]); + for (int j = 0; j <= i; j++) { + assertFalse(c[j].isClosed()); + } + } + + for (final Connection element : c) { + element.close(); + } + } + + /** + * Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS when setPoolPreparedStatements(true) + */ + @Test + public void testPoolPrepareCall() throws SQLException { + pcds.setPoolPreparedStatements(true); + try (final Connection conn = ds.getConnection()) { + assertNotNull(conn); + try (final PreparedStatement stmt = conn.prepareCall("{call home()}")) { + assertNotNull(stmt); + try (final ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + } + } + + @Test + public void testPoolPreparedCalls() throws Exception { + doTestPoolCallableStatements(new CscbString()); + doTestPoolCallableStatements(new CscbStringIntInt()); + doTestPoolCallableStatements(new CscbStringIntIntInt()); + } + + @Test + public void testPoolPreparedStatements() throws Exception { + doTestPoolPreparedStatements(new PscbString()); + doTestPoolPreparedStatements(new PscbStringIntInt()); + doTestPoolPreparedStatements(new PscbStringInt()); + doTestPoolPreparedStatements(new PscbStringIntArray()); + doTestPoolPreparedStatements(new PscbStringStringArray()); + doTestPoolPreparedStatements(new PscbStringIntIntInt()); + } + + @Test + public void testPoolPrepareStatement() throws SQLException { + pcds.setPoolPreparedStatements(true); + + try (final Connection conn = ds.getConnection()) { + assertNotNull(conn); + try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (final ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + } + } + + @Override + @Test + public void testSimple() throws Exception { + try (final Connection conn = ds.getConnection()) { + assertNotNull(conn); + try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (final ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + } + } + + @Override + @Test + public void testSimple2() throws SQLException { + { + final Connection conn = ds.getConnection(); + assertNotNull(conn); + + try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + conn.close(); + assertThrows(SQLException.class, () -> conn.createStatement(), "Can't use closed connections"); + } + try (Connection conn = ds.getConnection()) { + assertNotNull(conn); + + try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + try (PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + + } + } + + @Test + public void testSimpleWithUsername() throws Exception { + try (final Connection conn = ds.getConnection("u1", "p1")) { + assertNotNull(conn); + try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) { + assertNotNull(stmt); + try (final ResultSet rset = stmt.executeQuery()) { + assertNotNull(rset); + assertTrue(rset.next()); + } + } + } + } + + @Test + public void testTransactionIsolationBehavior() throws Exception { + try (final Connection conn = getConnection()) { + assertNotNull(conn); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + } + + final Connection conn2 = getConnection(); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn2.getTransactionIsolation()); + + final Connection conn3 = getConnection(); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn3.getTransactionIsolation()); + conn2.close(); + conn3.close(); + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/datasources/TestUserPassKey.java b/src/test/java/org/apache/commons/dbcp2/datasources/TestUserPassKey.java index c8b8555e7c..d590790342 100644 --- a/src/test/java/org/apache/commons/dbcp2/datasources/TestUserPassKey.java +++ b/src/test/java/org/apache/commons/dbcp2/datasources/TestUserPassKey.java @@ -1,78 +1,78 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.dbcp2.datasources; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import org.apache.commons.dbcp2.Utils; -import org.apache.commons.lang3.SerializationUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Tests for UserPassKey. - */ -public class TestUserPassKey { - - private UserPassKey userPassKey; - private UserPassKey anotherUserPassKey; - - @BeforeEach - public void setUp() { - userPassKey = new UserPassKey("user", "pass"); - anotherUserPassKey = new UserPassKey((String) null, ""); - } - - @Test - public void testEquals() { - assertEquals(new UserPassKey("user"), new UserPassKey("user", (char[]) null)); - assertEquals(userPassKey, userPassKey); - assertNotEquals(userPassKey, null); - assertNotEquals(userPassKey, new Object()); - assertNotEquals(new UserPassKey(null), userPassKey); - assertEquals(new UserPassKey(null), new UserPassKey(null)); - assertNotEquals(new UserPassKey("user", "pass"), new UserPassKey("foo", "pass")); - } - - @Test - public void testGettersAndSetters() { - assertEquals("user", userPassKey.getUserName()); - assertEquals("pass", userPassKey.getPassword()); - assertArrayEquals(Utils.toCharArray("pass"), userPassKey.getPasswordCharArray()); - } - - @Test - public void testHashcode() { - assertEquals(userPassKey.hashCode(), new UserPassKey("user", "pass").hashCode()); - assertNotEquals(userPassKey.hashCode(), anotherUserPassKey.hashCode()); - } - - @Test - public void testSerialization() { - assertEquals(userPassKey, SerializationUtils.roundtrip(userPassKey)); - assertEquals(anotherUserPassKey, SerializationUtils.roundtrip(anotherUserPassKey)); - } - - @Test - public void testToString() { - assertEquals(userPassKey.toString(), new UserPassKey("user", "pass").toString()); - assertNotEquals(userPassKey.toString(), anotherUserPassKey.toString()); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.dbcp2.datasources; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.apache.commons.dbcp2.Utils; +import org.apache.commons.lang3.SerializationUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for UserPassKey. + */ +public class TestUserPassKey { + + private UserPassKey userPassKey; + private UserPassKey anotherUserPassKey; + + @BeforeEach + public void setUp() { + userPassKey = new UserPassKey("user", "pass"); + anotherUserPassKey = new UserPassKey((String) null, ""); + } + + @Test + public void testEquals() { + assertEquals(new UserPassKey("user"), new UserPassKey("user", (char[]) null)); + assertEquals(userPassKey, userPassKey); + assertNotEquals(userPassKey, null); + assertNotEquals(userPassKey, new Object()); + assertNotEquals(new UserPassKey(null), userPassKey); + assertEquals(new UserPassKey(null), new UserPassKey(null)); + assertNotEquals(new UserPassKey("user", "pass"), new UserPassKey("foo", "pass")); + } + + @Test + public void testGettersAndSetters() { + assertEquals("user", userPassKey.getUserName()); + assertEquals("pass", userPassKey.getPassword()); + assertArrayEquals(Utils.toCharArray("pass"), userPassKey.getPasswordCharArray()); + } + + @Test + public void testHashcode() { + assertEquals(userPassKey.hashCode(), new UserPassKey("user", "pass").hashCode()); + assertNotEquals(userPassKey.hashCode(), anotherUserPassKey.hashCode()); + } + + @Test + public void testSerialization() { + assertEquals(userPassKey, SerializationUtils.roundtrip(userPassKey)); + assertEquals(anotherUserPassKey, SerializationUtils.roundtrip(anotherUserPassKey)); + } + + @Test + public void testToString() { + assertEquals(userPassKey.toString(), new UserPassKey("user", "pass").toString()); + assertNotEquals(userPassKey.toString(), anotherUserPassKey.toString()); + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java b/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java index fdc62627b0..0b949c86ca 100644 --- a/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java @@ -1,229 +1,229 @@ -/* - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.XADataSource; -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAException; - -import org.apache.commons.dbcp2.BasicDataSource; -import org.apache.commons.dbcp2.TestBasicDataSource; -import org.apache.geronimo.transaction.manager.TransactionManagerImpl; -import org.h2.Driver; -import org.h2.jdbcx.JdbcDataSource; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.ThrowingSupplier; - -import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; -import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple; - -/** - * TestSuite for BasicManagedDataSource - */ -public class TestBasicManagedDataSource extends TestBasicDataSource { - - @Override - protected BasicDataSource createDataSource() throws Exception { - final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource(); - final TransactionManagerImpl transactionManager = new TransactionManagerImpl(); - basicManagedDataSource.setTransactionManager(transactionManager); - basicManagedDataSource.setTransactionSynchronizationRegistry(transactionManager); - return basicManagedDataSource; - } - - @Test - public void testCreateXaDataSourceNewInstance() throws SQLException, XAException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setXADataSource(JdbcDataSource.class.getCanonicalName()); - basicManagedDataSource.setDriverClassName(Driver.class.getName()); - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - assertNotNull(basicManagedDataSource.createConnectionFactory()); - } - } - - @Test - public void testCreateXaDataSourceNoInstanceSetAndNoDataSource() throws SQLException, XAException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); - basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - assertNotNull(basicManagedDataSource.createConnectionFactory()); - } - } - - /** - * JIRA: DBCP-294 - * Verify that PoolableConnections created by BasicManagedDataSource unregister themselves - * when reallyClosed. - */ - @Test - public void testReallyClose() throws Exception { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); - basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); - basicManagedDataSource.setUsername("userName"); - basicManagedDataSource.setPassword("password"); - basicManagedDataSource.setMaxIdle(1); - // Create two connections - final ManagedConnection conn = (ManagedConnection) basicManagedDataSource.getConnection(); - assertNotNull(basicManagedDataSource.getTransactionRegistry().getXAResource(conn)); - final ManagedConnection conn2 = (ManagedConnection) basicManagedDataSource.getConnection(); - conn2.close(); // Return one connection to the pool - conn.close(); // No room at the inn - this will trigger reallyClose(), which should unregister - assertThrows(SQLException.class, () -> basicManagedDataSource.getTransactionRegistry().getXAResource(conn), - "Expecting SQLException - XAResources orphaned"); - conn2.close(); - } - } - - @Test - public void testRuntimeExceptionsAreRethrown() throws SQLException, XAException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); - basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); - basicManagedDataSource.setUsername("userName"); - basicManagedDataSource.setPassword("password"); - basicManagedDataSource.setMaxIdle(1); - // results in a NPE - assertThrows(NullPointerException.class, () -> basicManagedDataSource.createPoolableConnectionFactory(null)); - } - } - - @Test - public void testSetDriverName() throws SQLException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setDriverClassName("adams"); - assertEquals("adams", basicManagedDataSource.getDriverClassName()); - basicManagedDataSource.setDriverClassName(null); - assertNull(basicManagedDataSource.getDriverClassName()); - } - } - - @Test - public void testSetNullXaDataSourceInstance() throws SQLException, XAException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); - basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); - basicManagedDataSource.setUsername("userName"); - basicManagedDataSource.setPassword("password"); - basicManagedDataSource.setMaxIdle(1); - basicManagedDataSource.setXaDataSourceInstance(null); - assertNull(basicManagedDataSource.getXaDataSourceInstance()); - } - } - - /** DBCP-564 */ - @Test - public void testSetRollbackOnlyBeforeGetConnectionDoesNotLeak() throws Exception { - final TransactionManager transactionManager = ((BasicManagedDataSource) ds).getTransactionManager(); - final int n = 3; - ds.setMaxIdle(n); - ds.setMaxTotal(n); - - for (int i = 0; i <= n; i++) { // loop n+1 times - transactionManager.begin(); - transactionManager.setRollbackOnly(); - try (final Connection conn = getConnection()) { - assertNotNull(conn); - } - transactionManager.rollback(); - } - - assertEquals(0, ds.getNumActive()); - assertEquals(1, ds.getNumIdle()); - } - - @Test - public void testSetXaDataSourceInstance() throws SQLException, XAException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); - basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); - basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); - basicManagedDataSource.setUsername("userName"); - basicManagedDataSource.setPassword("password"); - basicManagedDataSource.setMaxIdle(1); - basicManagedDataSource.setXaDataSourceInstance(new JdbcDataSource()); - assertNotNull(basicManagedDataSource.createConnectionFactory()); - } - } - - @Test - public void testTransactionManagerNotSet() throws SQLException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - assertThrows(SQLException.class, basicManagedDataSource::createConnectionFactory); - } - } - - @Test - public void testTransactionSynchronizationRegistry() throws Exception { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setTransactionManager(new TransactionManagerImple()); - final TransactionSynchronizationRegistry tsr = new TransactionSynchronizationRegistryImple(); - basicManagedDataSource.setTransactionSynchronizationRegistry(tsr); - final JdbcDataSource xaDataSource = new JdbcDataSource(); - xaDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); - basicManagedDataSource.setXaDataSourceInstance(xaDataSource); - basicManagedDataSource.setMaxIdle(1); - - final TransactionManager tm = basicManagedDataSource.getTransactionManager(); - tm.begin(); - tsr.registerInterposedSynchronization(new SynchronizationAdapter() { - @Override - public void beforeCompletion() { - try (Connection connection = assertDoesNotThrow((ThrowingSupplier) basicManagedDataSource::getConnection)) { - assertNotNull(connection); - } catch (SQLException e) { - fail(e); - } - } - }); - tm.commit(); - } - } - - @Test - public void testXADataSource() throws SQLException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - basicManagedDataSource.setXADataSource("anything"); - assertEquals("anything", basicManagedDataSource.getXADataSource()); - } - } - - @Test - public void testXaDataSourceInstance() throws SQLException { - try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { - final XADataSource ds = new JdbcDataSource(); - basicManagedDataSource.setXaDataSourceInstance(ds); - assertEquals(ds, basicManagedDataSource.getXaDataSourceInstance()); - } - } -} +/* + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.XADataSource; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAException; + +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.dbcp2.TestBasicDataSource; +import org.apache.geronimo.transaction.manager.TransactionManagerImpl; +import org.h2.Driver; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingSupplier; + +import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; +import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple; + +/** + * TestSuite for BasicManagedDataSource + */ +public class TestBasicManagedDataSource extends TestBasicDataSource { + + @Override + protected BasicDataSource createDataSource() throws Exception { + final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource(); + final TransactionManagerImpl transactionManager = new TransactionManagerImpl(); + basicManagedDataSource.setTransactionManager(transactionManager); + basicManagedDataSource.setTransactionSynchronizationRegistry(transactionManager); + return basicManagedDataSource; + } + + @Test + public void testCreateXaDataSourceNewInstance() throws SQLException, XAException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setXADataSource(JdbcDataSource.class.getCanonicalName()); + basicManagedDataSource.setDriverClassName(Driver.class.getName()); + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + assertNotNull(basicManagedDataSource.createConnectionFactory()); + } + } + + @Test + public void testCreateXaDataSourceNoInstanceSetAndNoDataSource() throws SQLException, XAException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); + basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + assertNotNull(basicManagedDataSource.createConnectionFactory()); + } + } + + /** + * JIRA: DBCP-294 + * Verify that PoolableConnections created by BasicManagedDataSource unregister themselves + * when reallyClosed. + */ + @Test + public void testReallyClose() throws Exception { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); + basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); + basicManagedDataSource.setUsername("userName"); + basicManagedDataSource.setPassword("password"); + basicManagedDataSource.setMaxIdle(1); + // Create two connections + final ManagedConnection conn = (ManagedConnection) basicManagedDataSource.getConnection(); + assertNotNull(basicManagedDataSource.getTransactionRegistry().getXAResource(conn)); + final ManagedConnection conn2 = (ManagedConnection) basicManagedDataSource.getConnection(); + conn2.close(); // Return one connection to the pool + conn.close(); // No room at the inn - this will trigger reallyClose(), which should unregister + assertThrows(SQLException.class, () -> basicManagedDataSource.getTransactionRegistry().getXAResource(conn), + "Expecting SQLException - XAResources orphaned"); + conn2.close(); + } + } + + @Test + public void testRuntimeExceptionsAreRethrown() throws SQLException, XAException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); + basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); + basicManagedDataSource.setUsername("userName"); + basicManagedDataSource.setPassword("password"); + basicManagedDataSource.setMaxIdle(1); + // results in a NPE + assertThrows(NullPointerException.class, () -> basicManagedDataSource.createPoolableConnectionFactory(null)); + } + } + + @Test + public void testSetDriverName() throws SQLException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setDriverClassName("adams"); + assertEquals("adams", basicManagedDataSource.getDriverClassName()); + basicManagedDataSource.setDriverClassName(null); + assertNull(basicManagedDataSource.getDriverClassName()); + } + } + + @Test + public void testSetNullXaDataSourceInstance() throws SQLException, XAException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); + basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); + basicManagedDataSource.setUsername("userName"); + basicManagedDataSource.setPassword("password"); + basicManagedDataSource.setMaxIdle(1); + basicManagedDataSource.setXaDataSourceInstance(null); + assertNull(basicManagedDataSource.getXaDataSourceInstance()); + } + } + + /** DBCP-564 */ + @Test + public void testSetRollbackOnlyBeforeGetConnectionDoesNotLeak() throws Exception { + final TransactionManager transactionManager = ((BasicManagedDataSource) ds).getTransactionManager(); + final int n = 3; + ds.setMaxIdle(n); + ds.setMaxTotal(n); + + for (int i = 0; i <= n; i++) { // loop n+1 times + transactionManager.begin(); + transactionManager.setRollbackOnly(); + try (final Connection conn = getConnection()) { + assertNotNull(conn); + } + transactionManager.rollback(); + } + + assertEquals(0, ds.getNumActive()); + assertEquals(1, ds.getNumIdle()); + } + + @Test + public void testSetXaDataSourceInstance() throws SQLException, XAException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setTransactionManager(new TransactionManagerImpl()); + basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); + basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver"); + basicManagedDataSource.setUsername("userName"); + basicManagedDataSource.setPassword("password"); + basicManagedDataSource.setMaxIdle(1); + basicManagedDataSource.setXaDataSourceInstance(new JdbcDataSource()); + assertNotNull(basicManagedDataSource.createConnectionFactory()); + } + } + + @Test + public void testTransactionManagerNotSet() throws SQLException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + assertThrows(SQLException.class, basicManagedDataSource::createConnectionFactory); + } + } + + @Test + public void testTransactionSynchronizationRegistry() throws Exception { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setTransactionManager(new TransactionManagerImple()); + final TransactionSynchronizationRegistry tsr = new TransactionSynchronizationRegistryImple(); + basicManagedDataSource.setTransactionSynchronizationRegistry(tsr); + final JdbcDataSource xaDataSource = new JdbcDataSource(); + xaDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + basicManagedDataSource.setXaDataSourceInstance(xaDataSource); + basicManagedDataSource.setMaxIdle(1); + + final TransactionManager tm = basicManagedDataSource.getTransactionManager(); + tm.begin(); + tsr.registerInterposedSynchronization(new SynchronizationAdapter() { + @Override + public void beforeCompletion() { + try (Connection connection = assertDoesNotThrow((ThrowingSupplier) basicManagedDataSource::getConnection)) { + assertNotNull(connection); + } catch (SQLException e) { + fail(e); + } + } + }); + tm.commit(); + } + } + + @Test + public void testXADataSource() throws SQLException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + basicManagedDataSource.setXADataSource("anything"); + assertEquals("anything", basicManagedDataSource.getXADataSource()); + } + } + + @Test + public void testXaDataSourceInstance() throws SQLException { + try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) { + final XADataSource ds = new JdbcDataSource(); + basicManagedDataSource.setXaDataSourceInstance(ds); + assertEquals(ds, basicManagedDataSource.getXaDataSourceInstance()); + } + } +} diff --git a/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java b/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java index a353b3a586..f4a066fd69 100644 --- a/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java +++ b/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java @@ -1,380 +1,380 @@ -/* - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package org.apache.commons.dbcp2.managed; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import javax.transaction.Synchronization; -import javax.transaction.Transaction; - -import org.apache.commons.dbcp2.DelegatingConnection; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.ThrowingSupplier; - -/** - * Tests ManagedDataSource with an active transaction in progress. - */ -public class TestManagedDataSourceInTx extends TestManagedDataSource { - - // can't actually test close in a transaction - @Override - protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException { - assertFalse(conn.isClosed()); - assertFalse(isClosed(statement)); - - assertSame(conn, statement.getConnection(), - "statement.getConnection() should return the exact same connection instance that was used to create the statement"); - - try (ResultSet resultSet = statement.getResultSet()) { - assertFalse(isClosed(resultSet)); - assertSame(statement, resultSet.getStatement(), - "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); - - try (ResultSet executeResultSet = statement.executeQuery("select * from dual")) { - assertFalse(isClosed(executeResultSet)); - assertSame(statement, executeResultSet.getStatement(), - "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); - } - - try (ResultSet keysResultSet = statement.getGeneratedKeys()) { - assertFalse(isClosed(keysResultSet)); - assertSame(statement, keysResultSet.getStatement(), - "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); - } - if (statement instanceof PreparedStatement) { - final PreparedStatement preparedStatement = (PreparedStatement) statement; - try (ResultSet preparedResultSet = preparedStatement.executeQuery()) { - assertFalse(isClosed(preparedResultSet)); - assertSame(statement, preparedResultSet.getStatement(), - "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); - } - } - - resultSet.getStatement().getConnection().close(); - } - } - - @Override - @BeforeEach - public void setUp() throws Exception { - super.setUp(); - transactionManager.begin(); - } - - @Override - @AfterEach - public void tearDown() throws Exception { - if (transactionManager.getTransaction() != null) { - transactionManager.commit(); - } - super.tearDown(); - } - - @Override - @Test - public void testAutoCommitBehavior() throws Exception { - final Connection connection = newConnection(); - - // auto commit should be off - assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled"); - - // attempt to set auto commit - assertThrows(SQLException.class, () -> connection.setAutoCommit(true), "setAutoCommit method should be disabled while enlisted in a transaction"); - - // make sure it is still disabled - assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled"); - - // close connection - connection.close(); - } - - @Override - @Test - public void testClearWarnings() throws Exception { - // open a connection - Connection connection = newConnection(); - assertNotNull(connection); - - // generate SQLWarning on connection - final CallableStatement statement = connection.prepareCall("warning"); - assertNotNull(connection.getWarnings()); - - // create a new shared connection - final Connection sharedConnection = newConnection(); - - // shared connection should see warning - assertNotNull(sharedConnection.getWarnings()); - - // close and allocate a new (original) connection - connection.close(); - connection = newConnection(); - - // warnings should not have been cleared by closing the connection - assertNotNull(connection.getWarnings()); - assertNotNull(sharedConnection.getWarnings()); - - statement.close(); - sharedConnection.close(); - connection.close(); - } - - @Test - public void testCloseInTransaction() throws Exception { - try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); - DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { - assertNotEquals(connectionA, connectionB); - assertNotEquals(connectionB, connectionA); - assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); - assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); - } - - final Connection connection = newConnection(); - - assertFalse(connection.isClosed(), "Connection should be open"); - - connection.close(); - - assertTrue(connection.isClosed(), "Connection should be closed"); - } - - @Test - public void testCommit() throws Exception { - try (Connection connection = newConnection()) { - // connection should be open - assertFalse(connection.isClosed(), "Connection should be open"); - // attempt commit directly - assertThrows(SQLException.class, connection::commit, "commit method should be disabled while enlisted in a transaction"); - // make sure it is still open - assertFalse(connection.isClosed(), "Connection should be open"); - - } - } - - @Override - @Test - public void testConnectionReturnOnCommit() throws Exception { - // override with no-op test - } - - @Override - @Test - public void testConnectionsAreDistinct() throws Exception { - final Connection[] conn = new Connection[getMaxTotal()]; - for (int i = 0; i < conn.length; i++) { - conn[i] = newConnection(); - for (int j = 0; j < i; j++) { - // two connections should be distinct instances - Assertions.assertNotSame(conn[j], conn[i]); - // neither should they should be equivalent even though they are - // sharing the same underlying connection - Assertions.assertNotEquals(conn[j], conn[i]); - // Check underlying connection is the same - Assertions.assertEquals(((DelegatingConnection) conn[j]).getInnermostDelegateInternal(), - ((DelegatingConnection) conn[i]).getInnermostDelegateInternal()); - } - } - for (final Connection element : conn) { - element.close(); - } - } - - @Test - public void testDoubleReturn() throws Exception { - transactionManager.getTransaction().registerSynchronization(new Synchronization() { - private ManagedConnection conn; - - @Override - public void afterCompletion(final int i) { - final int numActive = pool.getNumActive(); - try { - conn.checkOpen(); - } catch (final Exception e) { - // Ignore - } - assertEquals(numActive, pool.getNumActive()); - assertDoesNotThrow(conn::close, "Should have been able to close the connection"); - // TODO Requires DBCP-515 assertTrue(numActive -1 == pool.getNumActive()); - } - - @Override - public void beforeCompletion() { - assertDoesNotThrow(() -> conn = (ManagedConnection) ds.getConnection(), "Could not get connection"); - } - }); - transactionManager.commit(); - } - - @Test - public void testGetConnectionInAfterCompletion() throws Exception { - try (DelegatingConnection connection = (DelegatingConnection) newConnection()) { - // Don't close so we can check it for warnings in afterCompletion - transactionManager.getTransaction().registerSynchronization(new SynchronizationAdapter() { - @Override - public void afterCompletion(final int i) { - final Connection connection1 = assertDoesNotThrow((ThrowingSupplier) ds::getConnection); - assertThrows(SQLException.class, () -> connection1.getWarnings(), "Could operate on closed connection"); - } - }); - } - transactionManager.commit(); - } - - @Override - @Test - public void testHashCode() throws Exception { - try (Connection conn1 = newConnection()) { - assertNotNull(conn1); - try (Connection conn2 = newConnection()) { - assertNotNull(conn2); - - // shared connections should not have the same hash code - Assertions.assertNotEquals(conn1.hashCode(), conn2.hashCode()); - } - } - } - - /** - * @see #testSharedConnection() - */ - @Override - @Test - public void testManagedConnectionEqualsFail() throws Exception { - // this test is invalid for managed connections since because - // two connections to the same datasource are supposed to share - // a single connection - } - - @Override - @Test - public void testMaxTotal() throws Exception { - final Transaction[] transactions = new Transaction[getMaxTotal()]; - final Connection[] c = new Connection[getMaxTotal()]; - for (int i = 0; i < c.length; i++) { - // create a new connection in the current transaction - c[i] = newConnection(); - assertNotNull(c[i]); - - // suspend the current transaction and start a new one - transactions[i] = transactionManager.suspend(); - assertNotNull(transactions[i]); - transactionManager.begin(); - } - - try { - assertThrows(SQLException.class, this::newConnection, "Allowed to open more than DefaultMaxTotal connections."); - // should only be able to open 10 connections, so this test should - // throw an exception - } finally { - transactionManager.commit(); - for (int i = 0; i < c.length; i++) { - transactionManager.resume(transactions[i]); - c[i].close(); - transactionManager.commit(); - } - } - } - - @Override - @Test - public void testNestedConnections() { - // Not supported - } - - @Test - public void testReadOnly() throws Exception { - try (Connection connection = newConnection()) { - // NOTE: This test class uses connections that are read-only by default - // connection should be read only - assertTrue(connection.isReadOnly(), "Connection be read-only"); - // attempt to setReadOnly - assertThrows(SQLException.class, () -> connection.setReadOnly(true), "setReadOnly method should be disabled while enlisted in a transaction"); - // make sure it is still read-only - assertTrue(connection.isReadOnly(), "Connection be read-only"); - // attempt to setReadonly - assertThrows(SQLException.class, () -> connection.setReadOnly(false), "setReadOnly method should be disabled while enlisted in a transaction"); - // make sure it is still read-only - assertTrue(connection.isReadOnly(), "Connection be read-only"); - // TwR closes the connection - } - } - - @Override - @Test - public void testSharedConnection() throws Exception { - try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); - DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { - assertNotEquals(connectionA, connectionB); - assertNotEquals(connectionB, connectionA); - assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); - assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); - } - } - - @Test - public void testSharedTransactionConversion() throws Exception { - try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); - DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { - // in a transaction the inner connections should be equal - assertNotEquals(connectionA, connectionB); - assertNotEquals(connectionB, connectionA); - assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); - assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); - - transactionManager.commit(); - - // use the connection so it adjusts to the completed transaction - connectionA.getAutoCommit(); - connectionB.getAutoCommit(); - - // no there is no transaction so inner connections should not be equal - assertNotEquals(connectionA, connectionB); - assertNotEquals(connectionB, connectionA); - assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); - assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); - - transactionManager.begin(); - - // use the connection so it adjusts to the new transaction - connectionA.getAutoCommit(); - connectionB.getAutoCommit(); - - // back in a transaction so inner connections should be equal again - assertNotEquals(connectionA, connectionB); - assertNotEquals(connectionB, connectionA); - assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); - assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); - } - } -} +/* + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package org.apache.commons.dbcp2.managed; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.transaction.Synchronization; +import javax.transaction.Transaction; + +import org.apache.commons.dbcp2.DelegatingConnection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingSupplier; + +/** + * Tests ManagedDataSource with an active transaction in progress. + */ +public class TestManagedDataSourceInTx extends TestManagedDataSource { + + // can't actually test close in a transaction + @Override + protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException { + assertFalse(conn.isClosed()); + assertFalse(isClosed(statement)); + + assertSame(conn, statement.getConnection(), + "statement.getConnection() should return the exact same connection instance that was used to create the statement"); + + try (ResultSet resultSet = statement.getResultSet()) { + assertFalse(isClosed(resultSet)); + assertSame(statement, resultSet.getStatement(), + "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); + + try (ResultSet executeResultSet = statement.executeQuery("select * from dual")) { + assertFalse(isClosed(executeResultSet)); + assertSame(statement, executeResultSet.getStatement(), + "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); + } + + try (ResultSet keysResultSet = statement.getGeneratedKeys()) { + assertFalse(isClosed(keysResultSet)); + assertSame(statement, keysResultSet.getStatement(), + "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); + } + if (statement instanceof PreparedStatement) { + final PreparedStatement preparedStatement = (PreparedStatement) statement; + try (ResultSet preparedResultSet = preparedStatement.executeQuery()) { + assertFalse(isClosed(preparedResultSet)); + assertSame(statement, preparedResultSet.getStatement(), + "resultSet.getStatement() should return the exact same statement instance that was used to create the result set"); + } + } + + resultSet.getStatement().getConnection().close(); + } + } + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + transactionManager.begin(); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + if (transactionManager.getTransaction() != null) { + transactionManager.commit(); + } + super.tearDown(); + } + + @Override + @Test + public void testAutoCommitBehavior() throws Exception { + final Connection connection = newConnection(); + + // auto commit should be off + assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled"); + + // attempt to set auto commit + assertThrows(SQLException.class, () -> connection.setAutoCommit(true), "setAutoCommit method should be disabled while enlisted in a transaction"); + + // make sure it is still disabled + assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled"); + + // close connection + connection.close(); + } + + @Override + @Test + public void testClearWarnings() throws Exception { + // open a connection + Connection connection = newConnection(); + assertNotNull(connection); + + // generate SQLWarning on connection + final CallableStatement statement = connection.prepareCall("warning"); + assertNotNull(connection.getWarnings()); + + // create a new shared connection + final Connection sharedConnection = newConnection(); + + // shared connection should see warning + assertNotNull(sharedConnection.getWarnings()); + + // close and allocate a new (original) connection + connection.close(); + connection = newConnection(); + + // warnings should not have been cleared by closing the connection + assertNotNull(connection.getWarnings()); + assertNotNull(sharedConnection.getWarnings()); + + statement.close(); + sharedConnection.close(); + connection.close(); + } + + @Test + public void testCloseInTransaction() throws Exception { + try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); + DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { + assertNotEquals(connectionA, connectionB); + assertNotEquals(connectionB, connectionA); + assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); + assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); + } + + final Connection connection = newConnection(); + + assertFalse(connection.isClosed(), "Connection should be open"); + + connection.close(); + + assertTrue(connection.isClosed(), "Connection should be closed"); + } + + @Test + public void testCommit() throws Exception { + try (Connection connection = newConnection()) { + // connection should be open + assertFalse(connection.isClosed(), "Connection should be open"); + // attempt commit directly + assertThrows(SQLException.class, connection::commit, "commit method should be disabled while enlisted in a transaction"); + // make sure it is still open + assertFalse(connection.isClosed(), "Connection should be open"); + + } + } + + @Override + @Test + public void testConnectionReturnOnCommit() throws Exception { + // override with no-op test + } + + @Override + @Test + public void testConnectionsAreDistinct() throws Exception { + final Connection[] conn = new Connection[getMaxTotal()]; + for (int i = 0; i < conn.length; i++) { + conn[i] = newConnection(); + for (int j = 0; j < i; j++) { + // two connections should be distinct instances + Assertions.assertNotSame(conn[j], conn[i]); + // neither should they should be equivalent even though they are + // sharing the same underlying connection + Assertions.assertNotEquals(conn[j], conn[i]); + // Check underlying connection is the same + Assertions.assertEquals(((DelegatingConnection) conn[j]).getInnermostDelegateInternal(), + ((DelegatingConnection) conn[i]).getInnermostDelegateInternal()); + } + } + for (final Connection element : conn) { + element.close(); + } + } + + @Test + public void testDoubleReturn() throws Exception { + transactionManager.getTransaction().registerSynchronization(new Synchronization() { + private ManagedConnection conn; + + @Override + public void afterCompletion(final int i) { + final int numActive = pool.getNumActive(); + try { + conn.checkOpen(); + } catch (final Exception e) { + // Ignore + } + assertEquals(numActive, pool.getNumActive()); + assertDoesNotThrow(conn::close, "Should have been able to close the connection"); + // TODO Requires DBCP-515 assertTrue(numActive -1 == pool.getNumActive()); + } + + @Override + public void beforeCompletion() { + assertDoesNotThrow(() -> conn = (ManagedConnection) ds.getConnection(), "Could not get connection"); + } + }); + transactionManager.commit(); + } + + @Test + public void testGetConnectionInAfterCompletion() throws Exception { + try (DelegatingConnection connection = (DelegatingConnection) newConnection()) { + // Don't close so we can check it for warnings in afterCompletion + transactionManager.getTransaction().registerSynchronization(new SynchronizationAdapter() { + @Override + public void afterCompletion(final int i) { + final Connection connection1 = assertDoesNotThrow((ThrowingSupplier) ds::getConnection); + assertThrows(SQLException.class, () -> connection1.getWarnings(), "Could operate on closed connection"); + } + }); + } + transactionManager.commit(); + } + + @Override + @Test + public void testHashCode() throws Exception { + try (Connection conn1 = newConnection()) { + assertNotNull(conn1); + try (Connection conn2 = newConnection()) { + assertNotNull(conn2); + + // shared connections should not have the same hash code + Assertions.assertNotEquals(conn1.hashCode(), conn2.hashCode()); + } + } + } + + /** + * @see #testSharedConnection() + */ + @Override + @Test + public void testManagedConnectionEqualsFail() throws Exception { + // this test is invalid for managed connections since because + // two connections to the same datasource are supposed to share + // a single connection + } + + @Override + @Test + public void testMaxTotal() throws Exception { + final Transaction[] transactions = new Transaction[getMaxTotal()]; + final Connection[] c = new Connection[getMaxTotal()]; + for (int i = 0; i < c.length; i++) { + // create a new connection in the current transaction + c[i] = newConnection(); + assertNotNull(c[i]); + + // suspend the current transaction and start a new one + transactions[i] = transactionManager.suspend(); + assertNotNull(transactions[i]); + transactionManager.begin(); + } + + try { + assertThrows(SQLException.class, this::newConnection, "Allowed to open more than DefaultMaxTotal connections."); + // should only be able to open 10 connections, so this test should + // throw an exception + } finally { + transactionManager.commit(); + for (int i = 0; i < c.length; i++) { + transactionManager.resume(transactions[i]); + c[i].close(); + transactionManager.commit(); + } + } + } + + @Override + @Test + public void testNestedConnections() { + // Not supported + } + + @Test + public void testReadOnly() throws Exception { + try (Connection connection = newConnection()) { + // NOTE: This test class uses connections that are read-only by default + // connection should be read only + assertTrue(connection.isReadOnly(), "Connection be read-only"); + // attempt to setReadOnly + assertThrows(SQLException.class, () -> connection.setReadOnly(true), "setReadOnly method should be disabled while enlisted in a transaction"); + // make sure it is still read-only + assertTrue(connection.isReadOnly(), "Connection be read-only"); + // attempt to setReadonly + assertThrows(SQLException.class, () -> connection.setReadOnly(false), "setReadOnly method should be disabled while enlisted in a transaction"); + // make sure it is still read-only + assertTrue(connection.isReadOnly(), "Connection be read-only"); + // TwR closes the connection + } + } + + @Override + @Test + public void testSharedConnection() throws Exception { + try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); + DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { + assertNotEquals(connectionA, connectionB); + assertNotEquals(connectionB, connectionA); + assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); + assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); + } + } + + @Test + public void testSharedTransactionConversion() throws Exception { + try (DelegatingConnection connectionA = (DelegatingConnection) newConnection(); + DelegatingConnection connectionB = (DelegatingConnection) newConnection()) { + // in a transaction the inner connections should be equal + assertNotEquals(connectionA, connectionB); + assertNotEquals(connectionB, connectionA); + assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); + assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); + + transactionManager.commit(); + + // use the connection so it adjusts to the completed transaction + connectionA.getAutoCommit(); + connectionB.getAutoCommit(); + + // no there is no transaction so inner connections should not be equal + assertNotEquals(connectionA, connectionB); + assertNotEquals(connectionB, connectionA); + assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); + assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); + + transactionManager.begin(); + + // use the connection so it adjusts to the new transaction + connectionA.getAutoCommit(); + connectionB.getAutoCommit(); + + // back in a transaction so inner connections should be equal again + assertNotEquals(connectionA, connectionB); + assertNotEquals(connectionB, connectionA); + assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate())); + assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate())); + } + } +}