From 32110f03911921d723a8ab0b0c1b5cb165c626d7 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Thu, 9 Nov 2023 16:34:53 +0100 Subject: [PATCH] fix: backport the pool fix by copying the upstream module --- DEPENDENCIES | 14 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- edc-extensions/sql-pool/README.md | 36 +++ edc-extensions/sql-pool/build.gradle.kts | 32 +++ .../pool/commons/CommonsConnectionPool.java | 170 ++++++++++++++ .../commons/CommonsConnectionPoolConfig.java | 157 +++++++++++++ .../CommonsConnectionPoolConfigKeys.java | 80 +++++++ ...CommonsConnectionPoolServiceExtension.java | 154 +++++++++++++ .../DriverManagerConnectionFactory.java | 42 ++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 ++ .../CommonsConnectionPoolConfigTest.java | 70 ++++++ ...onsConnectionPoolServiceExtensionTest.java | 160 ++++++++++++++ .../commons/CommonsConnectionPoolTest.java | 207 ++++++++++++++++++ .../DriverManagerConnectionFactoryTest.java | 61 ++++++ gradle/libs.versions.toml | 4 +- settings.gradle.kts | 1 + 19 files changed, 1194 insertions(+), 17 deletions(-) create mode 100644 edc-extensions/sql-pool/README.md create mode 100644 edc-extensions/sql-pool/build.gradle.kts create mode 100644 edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPool.java create mode 100644 edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfig.java create mode 100644 edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigKeys.java create mode 100644 edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtension.java create mode 100644 edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactory.java create mode 100644 edc-extensions/sql-pool/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigTest.java create mode 100644 edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtensionTest.java create mode 100644 edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolTest.java create mode 100644 edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactoryTest.java diff --git a/DEPENDENCIES b/DEPENDENCIES index 76311a4e1..7ae8fd739 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -142,9 +142,9 @@ maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.2, Apache- maven/mavencentral/io.swagger.core.v3/swagger-annotations/2.2.10, Apache-2.0, approved, #11362 maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.2, Apache-2.0, approved, #5929 maven/mavencentral/io.swagger.core.v3/swagger-core/2.2.10, Apache-2.0, approved, #9265 -maven/mavencentral/io.swagger.core.v3/swagger-integration-jakarta/2.2.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-integration-jakarta/2.2.2, Apache-2.0, approved, #11475 maven/mavencentral/io.swagger.core.v3/swagger-integration/2.2.10, Apache-2.0, approved, #10352 -maven/mavencentral/io.swagger.core.v3/swagger-jaxrs2-jakarta/2.2.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-jaxrs2-jakarta/2.2.2, Apache-2.0, approved, #11477 maven/mavencentral/io.swagger.core.v3/swagger-jaxrs2/2.2.10, Apache-2.0, approved, #9814 maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.2, Apache-2.0, approved, #5919 maven/mavencentral/io.swagger.core.v3/swagger-models/2.2.10, Apache-2.0, approved, #10353 @@ -179,7 +179,7 @@ maven/mavencentral/org.antlr/antlr4-runtime/4.9.3, BSD-3-Clause, approved, #322 maven/mavencentral/org.apache.commons/commons-compress/1.23.0, Apache-2.0 AND BSD-3-Clause, approved, #7506 maven/mavencentral/org.apache.commons/commons-lang3/3.11, Apache-2.0, approved, CQ22642 maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.apache.commons/commons-pool2/2.11.1, Apache-2.0, approved, CQ23795 +maven/mavencentral/org.apache.commons/commons-pool2/2.12.0, Apache-2.0 AND LicenseRef-Public-Domain, approved, #10843 maven/mavencentral/org.apache.groovy/groovy-bom/4.0.11, Apache-2.0, approved, #9266 maven/mavencentral/org.apache.groovy/groovy-json/4.0.11, Apache-2.0, approved, #7411 maven/mavencentral/org.apache.groovy/groovy-xml/4.0.11, Apache-2.0, approved, #10179 @@ -231,7 +231,6 @@ maven/mavencentral/org.eclipse.edc/control-plane-api-client-spi/0.2.1, Apache-2. maven/mavencentral/org.eclipse.edc/control-plane-core/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/control-plane-spi/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/core-spi/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/core-spi/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/data-plane-api/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/data-plane-aws-s3/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/data-plane-client/0.2.1, Apache-2.0, approved, technology.edc @@ -287,20 +286,14 @@ maven/mavencentral/org.eclipse.edc/policy-engine-spi/0.2.1, Apache-2.0, approved maven/mavencentral/org.eclipse.edc/policy-engine/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/policy-evaluator/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/policy-model/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-model/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/policy-spi/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/sql-core/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/sql-core/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/sql-lease/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/sql-pool-apache-commons/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/state-machine/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transaction-local/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transaction-spi/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transaction-spi/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transfer-core/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transfer-data-plane-spi/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transfer-data-plane/0.2.1, Apache-2.0, approved, technology.edc @@ -311,7 +304,6 @@ maven/mavencentral/org.eclipse.edc/transfer-spi/0.2.1, Apache-2.0, approved, tec maven/mavencentral/org.eclipse.edc/transform-core/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transform-spi/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/util/0.2.1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/util/0.3.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/validator-core/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/validator-spi/0.2.1, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/vault-azure/0.2.1, Apache-2.0, approved, technology.edc diff --git a/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts index a1cdab224..4f9bff615 100644 --- a/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts @@ -29,12 +29,12 @@ plugins { dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) + runtimeOnly(project(":edc-extensions:sql-pool")) runtimeOnly(project(":edc-extensions:edr:edr-cache-sql")) runtimeOnly(project(":edc-extensions:bpn-validation:business-partner-store-sql")) runtimeOnly(libs.edc.azure.vault) runtimeOnly(libs.bundles.edc.sqlstores) runtimeOnly(libs.edc.transaction.local) - runtimeOnly(libs.edc.sql.pool) runtimeOnly(libs.edc.core.controlplane) runtimeOnly(libs.edc.dpf.transfer) runtimeOnly(libs.postgres) diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 634c5a24f..1810a095e 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -29,12 +29,12 @@ plugins { dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) + runtimeOnly(project(":edc-extensions:sql-pool")) runtimeOnly(project(":edc-extensions:edr:edr-cache-sql")) runtimeOnly(project(":edc-extensions:bpn-validation:business-partner-store-sql")) runtimeOnly(libs.edc.vault.hashicorp) runtimeOnly(libs.bundles.edc.sqlstores) runtimeOnly(libs.edc.transaction.local) - runtimeOnly(libs.edc.sql.pool) runtimeOnly(libs.edc.core.controlplane) runtimeOnly(libs.edc.dpf.transfer) runtimeOnly(libs.postgres) diff --git a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts index 04993fa96..f2a9bafd3 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts @@ -34,8 +34,8 @@ dependencies { implementation(libs.edc.azure.identity) implementation("com.azure:azure-security-keyvault-secrets:4.6.4") runtimeOnly(project(":edc-extensions:edr:edr-cache-sql")) + runtimeOnly(project(":edc-extensions:sql-pool")) runtimeOnly(libs.edc.transaction.local) - runtimeOnly(libs.edc.sql.pool) runtimeOnly(libs.postgres) } diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts index 877952cf2..f596da4c4 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts @@ -27,8 +27,8 @@ dependencies { implementation(project(":edc-dataplane:edc-dataplane-base")) runtimeOnly(libs.edc.vault.hashicorp) runtimeOnly(project(":edc-extensions:edr:edr-cache-sql")) + runtimeOnly(project(":edc-extensions:sql-pool")) runtimeOnly(libs.edc.transaction.local) - runtimeOnly(libs.edc.sql.pool) runtimeOnly(libs.postgres) } diff --git a/edc-extensions/sql-pool/README.md b/edc-extensions/sql-pool/README.md new file mode 100644 index 000000000..dd44c03bd --- /dev/null +++ b/edc-extensions/sql-pool/README.md @@ -0,0 +1,36 @@ +# SQL Pool Apache Commons Pool + +This extension registers named `javax.sql.DataSource`s to +the `org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry` +capable of pooling `java.sql.Connection`s. The pooling mechanism is backed by +the [Apache Commons Pool library](https://commons.apache.org/proper/commons-pool/). + +## Old Configuration (Deprecated since 0.3.1) + +| Key | Description | Mandatory | +|:--------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------|-----------| +| edc.datasource..url | JDBC driver url | X | +| edc.datasource..pool.maxIdleConnections | The maximum amount of idling connections maintained by the pool | | +| edc.datasource..pool.maxTotalConnections | The maximum amount of total connections maintained by the pool | | +| edc.datasource..pool.minIdleConnections | The minimum amount of idling connections maintained by the pool | | +| edc.datasource..pool.testConnectionOnBorrow | Flag to define whether connections will be validated when a connection has been obtained from the pool | | +| edc.datasource..pool.testConnectionOnCreate | Flag to define whether connections will be validated when a connection has been established | | +| edc.datasource..pool.testConnectionOnReturn | Flag to define whether connections will be validated when a connection has been returned to the pool | | +| edc.datasource..pool.testConnectionWhileIdle | Flag to define whether idling connections will be validated | | +| edc.datasource..pool.testQuery | Test query to validate a connection maintained by the pool | | +| edc.datasource.. | JDBC driver specific configuration properties | | + +## New Configuration (since 0.3.1) + +| Key | Description | Mandatory | +|:-----------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------|-----------| +| edc.datasource..url | JDBC driver url | X | +| edc.datasource..pool.connections.max-idle | The maximum amount of idling connections maintained by the pool | | +| edc.datasource..pool.connections.max-total | The maximum amount of total connections maintained by the pool | | +| edc.datasource..pool.connections.min-idle | The minimum amount of idling connections maintained by the pool | | +| edc.datasource..pool.connection.test.on-borrow | Flag to define whether connections will be validated when a connection has been obtained from the pool | | +| edc.datasource..pool.connection.test.on-create | Flag to define whether connections will be validated when a connection has been established | | +| edc.datasource..pool.connection.test.on-return | Flag to define whether connections will be validated when a connection has been returned to the pool | | +| edc.datasource..pool.connection.test.while-idle | Flag to define whether idling connections will be validated | | +| edc.datasource..pool.connection.test.query | Test query to validate a connection maintained by the pool | | +| edc.datasource.. | JDBC driver specific configuration properties | | diff --git a/edc-extensions/sql-pool/build.gradle.kts b/edc-extensions/sql-pool/build.gradle.kts new file mode 100644 index 000000000..a35fdba34 --- /dev/null +++ b/edc-extensions/sql-pool/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Daimler TSS GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Daimler TSS GmbH - Initial build file + * + */ + +plugins { + `java-library` + `java-test-fixtures` + `maven-publish` +} + + +dependencies { + api(libs.edc.spi.transaction.datasource) + api(libs.edc.sql.core) + implementation(libs.apache.commons.pool) + + testImplementation(libs.edc.junit) + testImplementation(libs.edc.transaction.local) + +} + + diff --git a/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPool.java b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPool.java new file mode 100644 index 000000000..ab595a8a8 --- /dev/null +++ b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPool.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.DestroyMode; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.eclipse.edc.sql.pool.ConnectionPool; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Objects; +import javax.sql.DataSource; + +public final class CommonsConnectionPool implements ConnectionPool, AutoCloseable { + private final GenericObjectPool connectionObjectPool; + private final CommonsConnectionPoolConfig poolConfig; + + public CommonsConnectionPool(DataSource dataSource, CommonsConnectionPoolConfig commonsConnectionPoolConfig, Monitor monitor) { + this.poolConfig = commonsConnectionPoolConfig; + Objects.requireNonNull(dataSource, "connectionFactory"); + Objects.requireNonNull(commonsConnectionPoolConfig, "commonsConnectionPoolConfig"); + + this.connectionObjectPool = new GenericObjectPool<>( + new PooledConnectionObjectFactory(dataSource, commonsConnectionPoolConfig.getTestQuery(), monitor), + getGenericObjectPoolConfig(commonsConnectionPoolConfig)); + } + + private static GenericObjectPoolConfig getGenericObjectPoolConfig(CommonsConnectionPoolConfig commonsConnectionPoolConfig) { + GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig<>(); + + // no need for JMX + genericObjectPoolConfig.setJmxEnabled(false); + + genericObjectPoolConfig.setMaxIdle(commonsConnectionPoolConfig.getMaxIdleConnections()); + genericObjectPoolConfig.setMaxTotal(commonsConnectionPoolConfig.getMaxTotalConnections()); + genericObjectPoolConfig.setMinIdle(commonsConnectionPoolConfig.getMinIdleConnections()); + + genericObjectPoolConfig.setTestOnBorrow(commonsConnectionPoolConfig.getTestConnectionOnBorrow()); + genericObjectPoolConfig.setTestOnCreate(commonsConnectionPoolConfig.getTestConnectionOnCreate()); + genericObjectPoolConfig.setTestOnReturn(commonsConnectionPoolConfig.getTestConnectionOnReturn()); + genericObjectPoolConfig.setTestWhileIdle(commonsConnectionPoolConfig.getTestConnectionWhileIdle()); + + return genericObjectPoolConfig; + } + + @Override + public Connection getConnection() { + try { + return connectionObjectPool.borrowObject(); + } catch (Exception e) { + throw new EdcPersistenceException(e.getMessage(), e); + } + } + + @Override + public void returnConnection(Connection connection) { + Objects.requireNonNull(connection, "connection"); + + connectionObjectPool.returnObject(connection); + } + + @Override + public void close() { + connectionObjectPool.close(); + } + + public CommonsConnectionPoolConfig getPoolConfig() { + return poolConfig; + } + + private static class PooledConnectionObjectFactory extends BasePooledObjectFactory { + private final String testQuery; + private final DataSource dataSource; + + private final Monitor monitor; + + PooledConnectionObjectFactory(@NotNull DataSource dataSource, @NotNull String testQuery, Monitor monitor) { + this.dataSource = Objects.requireNonNull(dataSource); + this.testQuery = Objects.requireNonNull(testQuery); + this.monitor = monitor; + } + + @Override + public Connection create() throws SQLException { + return dataSource.getConnection(); + } + + @Override + public boolean validateObject(PooledObject pooledObject) { + if (pooledObject == null) { + return false; + } + + Connection connection = pooledObject.getObject(); + if (connection == null) { + return false; + } + + return isConnectionValid(connection); + } + + @Override + public PooledObject wrap(Connection connection) { + return new DefaultPooledObject<>(connection); + } + + @Override + public void destroyObject(PooledObject pooledObject, DestroyMode destroyMode) throws Exception { + if (pooledObject == null) { + return; + } + + Connection connection = pooledObject.getObject(); + + if (connection != null && !connection.isClosed()) { + connection.close(); + } + + pooledObject.invalidate(); + } + + private boolean isConnectionValid(Connection connection) { + try { + if (connection.isClosed()) { + return false; + } + + try (PreparedStatement preparedStatement = connection.prepareStatement(testQuery)) { + preparedStatement.execute(); + return rollbackIfNeeded(connection); + } + } catch (Exception e) { // any exception thrown indicates invalidity of the connection + return false; + } + } + + private boolean rollbackIfNeeded(Connection connection) { + try { + if (!connection.getAutoCommit()) { + connection.rollback(); + } + return true; + } catch (SQLException e) { + monitor.debug("Failed to rollback transaction", e); + } + return false; + } + + } +} diff --git a/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfig.java b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfig.java new file mode 100644 index 000000000..94aa4072e --- /dev/null +++ b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfig.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A CommonsConnectionPoolConfig is a container object containing a set of pool configuration + * parameters that can be used by the {@code CommonsConnectionPool} for connection management. + */ +public class CommonsConnectionPoolConfig { + + private final int maxIdleConnections; + private final int maxTotalConnections; + private final int minIdleConnections; + private final boolean testConnectionOnBorrow; + private final boolean testConnectionOnCreate; + private final boolean testConnectionOnReturn; + private final boolean testConnectionWhileIdle; + private final String testQuery; + + private CommonsConnectionPoolConfig( + int maxIdleConnections, + int maxTotalConnections, + int minIdleConnections, + boolean testConnectionOnBorrow, + boolean testConnectionOnCreate, + boolean testConnectionOnReturn, + boolean testConnectionWhileIdle, + @NotNull String testQuery) { + this.maxIdleConnections = maxIdleConnections; + this.maxTotalConnections = maxTotalConnections; + this.minIdleConnections = minIdleConnections; + this.testConnectionOnBorrow = testConnectionOnBorrow; + this.testConnectionOnCreate = testConnectionOnCreate; + this.testConnectionOnReturn = testConnectionOnReturn; + this.testConnectionWhileIdle = testConnectionWhileIdle; + this.testQuery = Objects.requireNonNull(testQuery); + } + + public int getMaxIdleConnections() { + return maxIdleConnections; + } + + public int getMaxTotalConnections() { + return maxTotalConnections; + } + + public int getMinIdleConnections() { + return minIdleConnections; + } + + public boolean getTestConnectionOnBorrow() { + return testConnectionOnBorrow; + } + + public boolean getTestConnectionOnCreate() { + return testConnectionOnCreate; + } + + public boolean getTestConnectionOnReturn() { + return testConnectionOnReturn; + } + + public boolean getTestConnectionWhileIdle() { + return testConnectionWhileIdle; + } + + public String getTestQuery() { + return testQuery; + } + + public static final class Builder { + private int maxIdleConnections = 4; + private int maxTotalConnections = 8; + private int minIdleConnections = 1; + private boolean testConnectionOnBorrow = true; + private boolean testConnectionOnCreate = true; + private boolean testConnectionOnReturn = false; + private boolean testConnectionWhileIdle = false; + private String testQuery = "SELECT 1;"; + + private Builder() { + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder maxIdleConnections(int maxIdleConnections) { + this.maxIdleConnections = maxIdleConnections; + return this; + } + + public Builder maxTotalConnections(int maxTotalConnections) { + this.maxTotalConnections = maxTotalConnections; + return this; + } + + public Builder minIdleConnections(int minIdleConnections) { + this.minIdleConnections = minIdleConnections; + return this; + } + + public Builder testConnectionOnBorrow(boolean testConnectionOnBorrow) { + this.testConnectionOnBorrow = testConnectionOnBorrow; + return this; + } + + public Builder testConnectionOnCreate(boolean testConnectionOnCreate) { + this.testConnectionOnCreate = testConnectionOnCreate; + return this; + } + + public Builder testConnectionOnReturn(boolean testConnectionOnReturn) { + this.testConnectionOnReturn = testConnectionOnReturn; + return this; + } + + public Builder testConnectionWhileIdle(boolean testConnectionWhileIdle) { + this.testConnectionWhileIdle = testConnectionWhileIdle; + return this; + } + + public Builder testQuery(String testQuery) { + this.testQuery = testQuery; + return this; + } + + public CommonsConnectionPoolConfig build() { + return new CommonsConnectionPoolConfig( + maxIdleConnections, + maxTotalConnections, + minIdleConnections, + testConnectionOnBorrow, + testConnectionOnCreate, + testConnectionOnReturn, + testConnectionWhileIdle, + testQuery + ); + } + } +} diff --git a/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigKeys.java b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigKeys.java new file mode 100644 index 000000000..cf3633f4e --- /dev/null +++ b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigKeys.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.eclipse.edc.runtime.metamodel.annotation.Setting; + +import java.util.Map; + +interface CommonsConnectionPoolConfigKeys { + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_MAX_IDLE_CONNECTIONS = "pool.maxIdleConnections"; + String POOL_CONNECTIONS_MAX_IDLE = "pool.connections.max-idle"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_MAX_TOTAL_CONNECTIONS = "pool.maxTotalConnections"; + + String POOL_CONNECTIONS_MAX_TOTAL = "pool.connections.max-total"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_MIN_IDLE_CONNECTIONS = "pool.minIdleConnections"; + String POOL_CONNECTIONS_MIN_IDLE = "pool.connections.min-idle"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_TEST_CONNECTION_ON_BORROW = "pool.testConnectionOnBorrow"; + String POOL_CONNECTION_TEST_ON_BORROW = "pool.connection.test.on-borrow"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_TEST_CONNECTION_ON_CREATE = "pool.testConnectionOnCreate"; + + String POOL_CONNECTION_TEST_ON_CREATE = "pool.connection.test.on-create"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_TEST_CONNECTION_ON_RETURN = "pool.testConnectionOnReturn"; + + String POOL_CONNECTION_TEST_ON_RETURN = "pool.connection.test.on-return"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_TEST_CONNECTION_WHILE_IDLE = "pool.testConnectionWhileIdle"; + String POOL_CONNECTION_TEST_WHILE_IDLE = "pool.connection.test.while-idle"; + + @Deprecated(since = "0.3.1") + @Setting(required = false) + String DEPRACATED_POOL_TEST_QUERY = "pool.testQuery"; + + String POOL_CONNECTION_TEST_QUERY = "pool.connection.test.query"; + + @Setting(required = true) + String URL = "url"; + + Map CONFIGURATION_MAPPING = Map.of( + POOL_CONNECTIONS_MAX_IDLE, DEPRACATED_POOL_MAX_IDLE_CONNECTIONS, + POOL_CONNECTIONS_MIN_IDLE, DEPRACATED_POOL_MIN_IDLE_CONNECTIONS, + POOL_CONNECTIONS_MAX_TOTAL, DEPRACATED_POOL_MAX_TOTAL_CONNECTIONS, + POOL_CONNECTION_TEST_ON_BORROW, DEPRACATED_POOL_TEST_CONNECTION_ON_BORROW, + POOL_CONNECTION_TEST_ON_CREATE, DEPRACATED_POOL_TEST_CONNECTION_ON_CREATE, + POOL_CONNECTION_TEST_ON_RETURN, DEPRACATED_POOL_TEST_CONNECTION_ON_RETURN, + POOL_CONNECTION_TEST_WHILE_IDLE, DEPRACATED_POOL_TEST_CONNECTION_WHILE_IDLE, + POOL_CONNECTION_TEST_QUERY, DEPRACATED_POOL_TEST_QUERY); + +} diff --git a/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtension.java b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtension.java new file mode 100644 index 000000000..64d7fb8cb --- /dev/null +++ b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtension.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.sql.ConnectionFactory; +import org.eclipse.edc.sql.datasource.ConnectionFactoryDataSource; +import org.eclipse.edc.sql.datasource.ConnectionPoolDataSource; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import javax.sql.DataSource; + +import static java.lang.String.format; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.CONFIGURATION_MAPPING; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MAX_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MAX_TOTAL; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MIN_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_BORROW; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_CREATE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_RETURN; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_QUERY; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_WHILE_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.URL; + +@Extension(value = CommonsConnectionPoolServiceExtension.NAME) +public class CommonsConnectionPoolServiceExtension implements ServiceExtension { + public static final String NAME = "Commons Connection Pool"; + public static final String EDC_DATASOURCE_PREFIX = "edc.datasource"; + private final List commonsConnectionPools = new LinkedList<>(); + @Inject + private DataSourceRegistry dataSourceRegistry; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + Config config = context.getConfig(EDC_DATASOURCE_PREFIX); + + Map namedConnectionPools = createConnectionPools(config); + + for (Map.Entry entry : namedConnectionPools.entrySet()) { + String dataSourceName = entry.getKey(); + CommonsConnectionPool commonsConnectionPool = entry.getValue(); + commonsConnectionPools.add(commonsConnectionPool); + ConnectionPoolDataSource connectionPoolDataSource = new ConnectionPoolDataSource(commonsConnectionPool); + dataSourceRegistry.register(dataSourceName, connectionPoolDataSource); + } + } + + @Override + public void shutdown() { + for (CommonsConnectionPool commonsConnectionPool : commonsConnectionPools) { + commonsConnectionPool.close(); + } + } + + public List getCommonsConnectionPools() { + return commonsConnectionPools; + } + + private void setIfProvidedString(String key, Consumer setter, Config config) { + setIfProvided(key, setter, config::getString); + } + + private void setIfProvidedBoolean(String key, Consumer setter, Config config) { + setIfProvided(key, setter, config::getBoolean); + } + + private void setIfProvidedInt(String key, Consumer setter, Config config) { + setIfProvided(key, setter, config::getInteger); + } + + private void setIfProvided(String key, Consumer setter, BiFunction getter) { + var oldKey = CONFIGURATION_MAPPING.get(key); + var oldValue = getter.apply(oldKey, null); + if (oldValue != null) { + monitor.warning(format("Configuration setting %s has been deprecated, please use %s instead", oldKey, key)); + } + var value = getter.apply(key, oldValue); + if (value != null) { + setter.accept(value); + } + } + + private Map createConnectionPools(Config parent) { + Map commonsConnectionPools = new HashMap<>(); + for (Config config : parent.partition().toList()) { + String dataSourceName = config.currentNode(); + + DataSource dataSource = createDataSource(config); + + CommonsConnectionPool commonsConnectionPool = createConnectionPool(dataSource, config); + commonsConnectionPools.put(dataSourceName, commonsConnectionPool); + } + return commonsConnectionPools; + } + + private DataSource createDataSource(Config config) { + String jdbcUrl = Objects.requireNonNull(config.getString(URL)); + + Properties properties = new Properties(); + properties.putAll(config.getRelativeEntries()); + + ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(jdbcUrl, properties); + + return new ConnectionFactoryDataSource(connectionFactory); + } + + private CommonsConnectionPool createConnectionPool(DataSource unPooledDataSource, Config config) { + CommonsConnectionPoolConfig.Builder builder = CommonsConnectionPoolConfig.Builder.newInstance(); + + setIfProvidedInt(POOL_CONNECTIONS_MAX_IDLE, builder::maxIdleConnections, config); + setIfProvidedInt(POOL_CONNECTIONS_MAX_TOTAL, builder::maxTotalConnections, config); + setIfProvidedInt(POOL_CONNECTIONS_MIN_IDLE, builder::minIdleConnections, config); + setIfProvidedBoolean(POOL_CONNECTION_TEST_ON_BORROW, builder::testConnectionOnBorrow, config); + setIfProvidedBoolean(POOL_CONNECTION_TEST_ON_CREATE, builder::testConnectionOnCreate, config); + setIfProvidedBoolean(POOL_CONNECTION_TEST_ON_RETURN, builder::testConnectionOnReturn, config); + setIfProvidedBoolean(POOL_CONNECTION_TEST_WHILE_IDLE, builder::testConnectionWhileIdle, config); + setIfProvidedString(POOL_CONNECTION_TEST_QUERY, builder::testQuery, config); + + return new CommonsConnectionPool(unPooledDataSource, builder.build(), monitor); + } +} diff --git a/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactory.java b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactory.java new file mode 100644 index 000000000..a4077287b --- /dev/null +++ b/edc-extensions/sql-pool/src/main/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.eclipse.edc.sql.ConnectionFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Objects; +import java.util.Properties; + +public class DriverManagerConnectionFactory implements ConnectionFactory { + private final String jdbcUrl; + private final Properties properties; + + public DriverManagerConnectionFactory(String jdbcUrl, Properties properties) { + this.jdbcUrl = Objects.requireNonNull(jdbcUrl); + this.properties = Objects.requireNonNull(properties); + } + + @Override + public Connection create() { + try { + return DriverManager.getConnection(jdbcUrl, properties); + } catch (Exception exception) { + throw new EdcPersistenceException(exception.getMessage(), exception); + } + } +} diff --git a/edc-extensions/sql-pool/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/sql-pool/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..68ed49d43 --- /dev/null +++ b/edc-extensions/sql-pool/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2020, 2021 Microsoft Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Microsoft Corporation - initial API and implementation +# +# + +org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolServiceExtension diff --git a/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigTest.java b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigTest.java new file mode 100644 index 000000000..c04152bb4 --- /dev/null +++ b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolConfigTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CommonsConnectionPoolConfigTest { + + @Test + void testDefaults() { + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + + assertEquals(4, commonsConnectionPoolConfig.getMaxIdleConnections()); + assertEquals(8, commonsConnectionPoolConfig.getMaxTotalConnections()); + assertEquals(1, commonsConnectionPoolConfig.getMinIdleConnections()); + assertTrue(commonsConnectionPoolConfig.getTestConnectionOnBorrow()); + assertTrue(commonsConnectionPoolConfig.getTestConnectionOnCreate()); + assertFalse(commonsConnectionPoolConfig.getTestConnectionOnReturn()); + assertFalse(commonsConnectionPoolConfig.getTestConnectionWhileIdle()); + assertEquals("SELECT 1;", commonsConnectionPoolConfig.getTestQuery()); + } + + @Test + void test() { + var minIdleConnections = 1; + var maxIdleConnections = 2; + var maxTotalConnections = 3; + var testConnectionOnBorrow = true; + var testConnectionOnCreate = false; + var testConnectionWhileIdle = true; + var testConnectionOnReturn = false; + var testQuery = "testquery"; + + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance() + .maxIdleConnections(maxIdleConnections) + .maxTotalConnections(maxTotalConnections) + .minIdleConnections(minIdleConnections) + .testConnectionOnBorrow(testConnectionOnBorrow) + .testConnectionOnCreate(testConnectionOnCreate) + .testConnectionOnReturn(testConnectionOnReturn) + .testConnectionWhileIdle(testConnectionWhileIdle) + .testQuery(testQuery) + .build(); + + assertEquals(maxIdleConnections, commonsConnectionPoolConfig.getMaxIdleConnections()); + assertEquals(maxTotalConnections, commonsConnectionPoolConfig.getMaxTotalConnections()); + assertEquals(minIdleConnections, commonsConnectionPoolConfig.getMinIdleConnections()); + assertEquals(testConnectionOnBorrow, commonsConnectionPoolConfig.getTestConnectionOnBorrow()); + assertEquals(testConnectionOnCreate, commonsConnectionPoolConfig.getTestConnectionOnCreate()); + assertEquals(testConnectionOnReturn, commonsConnectionPoolConfig.getTestConnectionOnReturn()); + assertEquals(testConnectionWhileIdle, commonsConnectionPoolConfig.getTestConnectionWhileIdle()); + assertEquals(testQuery, commonsConnectionPoolConfig.getTestQuery()); + } +} diff --git a/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtensionTest.java b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtensionTest.java new file mode 100644 index 000000000..aa282f744 --- /dev/null +++ b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolServiceExtensionTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.assertj.core.api.ThrowingConsumer; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_MAX_IDLE_CONNECTIONS; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_MAX_TOTAL_CONNECTIONS; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_MIN_IDLE_CONNECTIONS; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_TEST_CONNECTION_ON_BORROW; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_TEST_CONNECTION_ON_CREATE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_TEST_CONNECTION_ON_RETURN; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_TEST_CONNECTION_WHILE_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.DEPRACATED_POOL_TEST_QUERY; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MAX_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MAX_TOTAL; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTIONS_MIN_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_BORROW; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_CREATE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_ON_RETURN; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_QUERY; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolConfigKeys.POOL_CONNECTION_TEST_WHILE_IDLE; +import static org.eclipse.tractusx.edc.sql.pool.commons.CommonsConnectionPoolServiceExtension.EDC_DATASOURCE_PREFIX; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +//sometimes hangs and causes the test to never finish. +@ExtendWith(DependencyInjectionExtension.class) +class CommonsConnectionPoolServiceExtensionTest { + private static final String DS_1_NAME = "ds1"; + private final DataSourceRegistry dataSourceRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(DataSourceRegistry.class, dataSourceRegistry); + } + + @ParameterizedTest + @ArgumentsSource(ConfigProvider.class) + void initialize_withConfig(Map configuration, ThrowingConsumer checker, + boolean isEnv, + CommonsConnectionPoolServiceExtension extension, ServiceExtensionContext context) { + Config config; + if (isEnv) { + config = ConfigFactory.fromEnvironment(configuration); + } else { + config = ConfigFactory.fromMap(configuration); + } + when(context.getConfig(EDC_DATASOURCE_PREFIX)).thenReturn(config); + + extension.initialize(context); + + verify(dataSourceRegistry).register(eq(DS_1_NAME), any()); + + assertThat(extension.getCommonsConnectionPools()).hasSize(1).first() + .extracting(CommonsConnectionPool::getPoolConfig) + .satisfies(checker); + } + + + static class ConfigProvider implements ArgumentsProvider { + + private final Map defaultConfig = Map.of(DS_1_NAME + ".url", DS_1_NAME); + + private final Map configuration = Map.of( + DS_1_NAME + ".url", DS_1_NAME, + DS_1_NAME + "." + POOL_CONNECTION_TEST_ON_CREATE, "false", + DS_1_NAME + "." + POOL_CONNECTION_TEST_ON_BORROW, "false", + DS_1_NAME + "." + POOL_CONNECTION_TEST_ON_RETURN, "true", + DS_1_NAME + "." + POOL_CONNECTION_TEST_WHILE_IDLE, "true", + DS_1_NAME + "." + POOL_CONNECTION_TEST_QUERY, "SELECT foo FROM bar;", + DS_1_NAME + "." + POOL_CONNECTIONS_MIN_IDLE, "10", + DS_1_NAME + "." + POOL_CONNECTIONS_MAX_IDLE, "10", + DS_1_NAME + "." + POOL_CONNECTIONS_MAX_TOTAL, "10"); + + + private final Map deprecatedConfig = Map.of( + DS_1_NAME + ".url", DS_1_NAME, + DS_1_NAME + "." + DEPRACATED_POOL_TEST_CONNECTION_ON_CREATE, "false", + DS_1_NAME + "." + DEPRACATED_POOL_TEST_CONNECTION_ON_BORROW, "false", + DS_1_NAME + "." + DEPRACATED_POOL_TEST_CONNECTION_ON_RETURN, "true", + DS_1_NAME + "." + DEPRACATED_POOL_TEST_CONNECTION_WHILE_IDLE, "true", + DS_1_NAME + "." + DEPRACATED_POOL_TEST_QUERY, "SELECT foo FROM bar;", + DS_1_NAME + "." + DEPRACATED_POOL_MIN_IDLE_CONNECTIONS, "10", + DS_1_NAME + "." + DEPRACATED_POOL_MAX_IDLE_CONNECTIONS, "10", + DS_1_NAME + "." + DEPRACATED_POOL_MAX_TOTAL_CONNECTIONS, "10"); + + @Override + public Stream provideArguments(ExtensionContext context) { + ThrowingConsumer checkDefault = this::checkDefault; + ThrowingConsumer checkWithConfig = this::checkWithConfig; + + var envConfiguration = configuration.entrySet().stream() + .map(it -> Map.entry(it.getKey().toUpperCase().replace(".", "_"), it.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return Stream.of( + Arguments.of(defaultConfig, checkDefault, false), + Arguments.of(configuration, checkWithConfig, false), + Arguments.of(envConfiguration, checkWithConfig, true), + Arguments.of(deprecatedConfig, checkWithConfig, false) + ); + } + + private void checkDefault(CommonsConnectionPoolConfig cfg) { + assertThat(cfg.getTestConnectionOnCreate()).isTrue(); + assertThat(cfg.getTestConnectionOnBorrow()).isTrue(); + assertThat(cfg.getTestConnectionOnReturn()).isFalse(); + assertThat(cfg.getTestConnectionWhileIdle()).isFalse(); + assertThat(cfg.getTestQuery()).isEqualTo("SELECT 1;"); + assertThat(cfg.getMinIdleConnections()).isEqualTo(1); + assertThat(cfg.getMaxIdleConnections()).isEqualTo(4); + assertThat(cfg.getMaxTotalConnections()).isEqualTo(8); + } + + private void checkWithConfig(CommonsConnectionPoolConfig cfg) { + assertThat(cfg.getTestConnectionOnCreate()).isFalse(); + assertThat(cfg.getTestConnectionOnBorrow()).isFalse(); + assertThat(cfg.getTestConnectionOnReturn()).isTrue(); + assertThat(cfg.getTestConnectionWhileIdle()).isTrue(); + assertThat(cfg.getTestQuery()).isEqualTo("SELECT foo FROM bar;"); + assertThat(cfg.getMinIdleConnections()).isEqualTo(10); + assertThat(cfg.getMaxIdleConnections()).isEqualTo(10); + assertThat(cfg.getMaxTotalConnections()).isEqualTo(10); + } + + } + +} diff --git a/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolTest.java b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolTest.java new file mode 100644 index 000000000..12840a63d --- /dev/null +++ b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/CommonsConnectionPoolTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import javax.sql.DataSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CommonsConnectionPoolTest { + + private final Monitor monitor = mock(); + + @Test + void getConnection() throws SQLException { + var connection = mock(Connection.class); + var testQueryPreparedStatement = mock(PreparedStatement.class); + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + when(testQueryPreparedStatement.execute()).thenReturn(true); + when(connection.prepareStatement(anyString())).thenReturn(testQueryPreparedStatement); + when(dataSource.getConnection()).thenReturn(connection); + + var result = connectionPool.getConnection(); + + assertNotNull(connection); + assertEquals(connection, result); + + verify(dataSource, atLeastOnce()).getConnection(); + verify(connection, atLeastOnce()).isClosed(); + verify(connection, atLeastOnce()).prepareStatement(anyString()); + verify(testQueryPreparedStatement, atLeastOnce()).execute(); + } + + @Test + void getConnectionAnyExceptionThrownThrowsSqlException() throws SQLException { + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + var causingRuntimeException = new RuntimeException("intended to be thrown"); + + when(dataSource.getConnection()).thenThrow(causingRuntimeException); + + var exceptionWrappingRuntimeException = assertThrows(EdcPersistenceException.class, connectionPool::getConnection); + + assertNotNull(exceptionWrappingRuntimeException.getCause()); + assertEquals(causingRuntimeException, exceptionWrappingRuntimeException.getCause()); + + verify(dataSource, atLeastOnce()).getConnection(); + } + + @Test + void getConnectionSqlExceptionThrownThrowsSame() throws SQLException { + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + var causingSqlException = new SQLException("intended to be thrown"); + + when(dataSource.getConnection()).thenThrow(causingSqlException); + + var sqlException = assertThrows(EdcPersistenceException.class, connectionPool::getConnection); + + assertNotNull(sqlException.getCause()); + assertEquals(causingSqlException, sqlException.getCause()); + + verify(dataSource, atLeastOnce()).getConnection(); + } + + @Test + void returnConnectionNullThrowsNullPointerException() { + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + assertThrows(NullPointerException.class, () -> connectionPool.returnConnection(null)); + } + + @Test + void returnConnectionUnknownThrowsIllegalStateException() { + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + // a connection unmanaged by the pool + var connection = mock(Connection.class); + + assertThrows(IllegalStateException.class, () -> connectionPool.returnConnection(connection)); + } + + @Test + void returnConnection() throws SQLException { + var connection = mock(Connection.class); + PreparedStatement testQueryPreparedStatement = mock(PreparedStatement.class); + var dataSource = mock(DataSource.class); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + when(testQueryPreparedStatement.execute()).thenReturn(true); + when(connection.prepareStatement(anyString())).thenReturn(testQueryPreparedStatement); + when(dataSource.getConnection()).thenReturn(connection); + + var result = connectionPool.getConnection(); + + assertNotNull(connection); + assertEquals(connection, result); + + connectionPool.returnConnection(result); + + verify(dataSource, atLeastOnce()).getConnection(); + verify(connection, atLeastOnce()).isClosed(); + verify(connection, atLeastOnce()).prepareStatement(anyString()); + verify(testQueryPreparedStatement, atLeastOnce()).execute(); + verify(connection, atLeastOnce()).rollback(); + + } + + @Test + void returnConnection_shouldInvalidateConnection_rollbackFailure() throws SQLException { + var connection = mock(Connection.class); + var testQueryPreparedStatement = mock(PreparedStatement.class); + when(testQueryPreparedStatement.execute()).thenReturn(true); + when(connection.prepareStatement(anyString())).thenReturn(testQueryPreparedStatement); + var dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenReturn(connection); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance() + .testConnectionOnCreate(false) + .testConnectionOnBorrow(false) + .testConnectionOnReturn(true) + .build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + var result = connectionPool.getConnection(); + + assertNotNull(connection); + assertEquals(connection, result); + + when(connection.isClosed()).thenReturn(false); + + doThrow(new SQLException()).when(connection).rollback(); + + connectionPool.returnConnection(connection); + + verify(dataSource, atLeastOnce()).getConnection(); + verify(connection, atLeastOnce()).isClosed(); + verify(connection, atLeastOnce()).prepareStatement(anyString()); + verify(testQueryPreparedStatement, atLeastOnce()).execute(); + verify(connection, atLeastOnce()).close(); + + verify(connection).rollback(); + + } + + @Test + void closeProperlyClosesManagedConnections() throws SQLException { + var connection = mock(Connection.class); + var testQueryPreparedStatement = mock(PreparedStatement.class); + when(testQueryPreparedStatement.execute()).thenReturn(true); + when(connection.prepareStatement(anyString())).thenReturn(testQueryPreparedStatement); + var dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenReturn(connection); + var commonsConnectionPoolConfig = CommonsConnectionPoolConfig.Builder.newInstance().build(); + var connectionPool = new CommonsConnectionPool(dataSource, commonsConnectionPoolConfig, monitor); + + var result = connectionPool.getConnection(); + + assertNotNull(connection); + assertEquals(connection, result); + + connectionPool.returnConnection(connection); + + connectionPool.close(); + + verify(dataSource, atLeastOnce()).getConnection(); + verify(connection, atLeastOnce()).isClosed(); + verify(connection, atLeastOnce()).prepareStatement(anyString()); + verify(testQueryPreparedStatement, atLeastOnce()).execute(); + verify(connection, atLeastOnce()).close(); + } +} diff --git a/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactoryTest.java b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactoryTest.java new file mode 100644 index 000000000..8d20a2b1b --- /dev/null +++ b/edc-extensions/sql-pool/src/test/java/org/eclipse/tractusx/edc/sql/pool/commons/DriverManagerConnectionFactoryTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.sql.pool.commons; + +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +public class DriverManagerConnectionFactoryTest { + private static final String DS_NAME = "datasource"; + private final Connection connection = mock(); + private DriverManagerConnectionFactory factory; + + @BeforeEach + void setup() { + factory = new DriverManagerConnectionFactory(DS_NAME, new Properties()); + } + + @Test + void create() throws SQLException { + try (var driverManagerMock = mockStatic(DriverManager.class)) { + driverManagerMock.when(() -> DriverManager.getConnection(eq(DS_NAME), any(Properties.class))).thenReturn(connection); + try (var conn = factory.create()) { + assertThat(conn).isEqualTo(connection); + } + } + } + + @Test + void create_shouldThrowException() { + try (var driverManagerMock = mockStatic(DriverManager.class)) { + driverManagerMock.when(() -> DriverManager.getConnection(eq(DS_NAME), any(Properties.class))) + .thenThrow(SQLException.class); + assertThatThrownBy(() -> factory.create()).isInstanceOf(EdcPersistenceException.class); + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 623649b1f..62eedbb9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ format.version = "1.1" [versions] edc = "0.2.1" -edcNext = "0.3.1" +apacheCommonsPool2 = "2.12.0" postgres = "42.6.0" awaitility = "4.2.0" nimbus = "9.31" @@ -89,7 +89,6 @@ edc-sql-transferprocess = { module = "org.eclipse.edc:transfer-process-store-sql edc-sql-policydef = { module = "org.eclipse.edc:policy-definition-store-sql", version.ref = "edc" } edc-sql-core = { module = "org.eclipse.edc:sql-core", version.ref = "edc" } edc-sql-lease = { module = "org.eclipse.edc:sql-lease", version.ref = "edc" } -edc-sql-pool = { module = "org.eclipse.edc:sql-pool-apache-commons", version.ref = "edcNext" } # azure stuff edc-azure-vault = { module = "org.eclipse.edc:vault-azure", version.ref = "edc" } @@ -149,6 +148,7 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } tink = { module = "com.google.crypto.tink:tink", version.ref = "tink" } apicatalog-iron-vc = { module = "com.apicatalog:iron-verifiable-credentials", version.ref = "iron-vc" } +apache-commons-pool = { module = "org.apache.commons:commons-pool2", version.ref = "apacheCommonsPool2" } [bundles] edc-connector = ["edc.boot", "edc.core-connector", "edc.core-controlplane", "edc.api-observability"] diff --git a/settings.gradle.kts b/settings.gradle.kts index 86486df88..2be4c8d8e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,6 +41,7 @@ include(":edc-extensions:cx-oauth2") include(":edc-extensions:data-encryption") include(":edc-extensions:dataplane-selector-configuration") include(":edc-extensions:postgresql-migration") +include(":edc-extensions:sql-pool") include(":edc-extensions:provision-additional-headers") include(":edc-extensions:transferprocess-sftp-client") include(":edc-extensions:transferprocess-sftp-common")