From a1ec1f61348f1f2425f9bc7fd175e67ce5c90656 Mon Sep 17 00:00:00 2001 From: Ramnivas Laddad Date: Mon, 21 Oct 2013 19:09:25 -0700 Subject: [PATCH] Support Tomcat's high-performance connection pool In the process, refactored code towards allowing user-contributable pool implementation. --- spring-service-connector/pom.xml | 6 ++ .../BasicDbcpPooledDataSourceCreator.java | 31 ++++++++++ .../service/relational/DataSourceCreator.java | 60 +++++++------------ .../DbcpLikePooledDataSourceCreator.java | 38 ++++++++++++ .../relational/PooledDataSourceCreator.java | 19 ++++++ .../TomcatDbcpPooledDataSourceCreator.java | 31 ++++++++++ ...ighPerformancePooledDataSourceCreator.java | 32 ++++++++++ .../PooledDataSourceCreatorsTest.java | 42 +++++++++++++ 8 files changed, 222 insertions(+), 37 deletions(-) create mode 100644 spring-service-connector/src/main/java/org/springframework/cloud/service/relational/BasicDbcpPooledDataSourceCreator.java create mode 100644 spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DbcpLikePooledDataSourceCreator.java create mode 100644 spring-service-connector/src/main/java/org/springframework/cloud/service/relational/PooledDataSourceCreator.java create mode 100644 spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatDbcpPooledDataSourceCreator.java create mode 100644 spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatHighPerformancePooledDataSourceCreator.java create mode 100644 spring-service-connector/src/test/java/org/springframework/cloud/service/relational/PooledDataSourceCreatorsTest.java diff --git a/spring-service-connector/pom.xml b/spring-service-connector/pom.xml index 82ac25c..178b843 100644 --- a/spring-service-connector/pom.xml +++ b/spring-service-connector/pom.xml @@ -84,6 +84,12 @@ ${tomcat.version} provided + + org.apache.tomcat + tomcat-dbcp + ${tomcat.version} + provided + commons-dbcp diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/BasicDbcpPooledDataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/BasicDbcpPooledDataSourceCreator.java new file mode 100644 index 0000000..0b07963 --- /dev/null +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/BasicDbcpPooledDataSourceCreator.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.service.relational; + +import static org.springframework.cloud.service.Util.hasClass; + +import javax.sql.DataSource; + +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RelationalServiceInfo; + +/** + * + * @author Ramnivas Laddad + * + * @param + */ +public class BasicDbcpPooledDataSourceCreator extends DbcpLikePooledDataSourceCreator { + + @Override + public DataSource create(RelationalServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfig, + String driverClassName, String validationQuery) { + if (hasClass("org.apache.commons.dbcp.BasicDataSource")) { + logger.info("Found DBCP on the classpath. Using it for DataSource connection pooling."); + org.apache.commons.dbcp.BasicDataSource ds = new org.apache.commons.dbcp.BasicDataSource(); + setBasicDataSourceProperties(ds, serviceInfo, serviceConnectorConfig, driverClassName, validationQuery); + return ds; + } else { + return null; + } + } + +} diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DataSourceCreator.java index 888eadb..2c9c1e3 100644 --- a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DataSourceCreator.java +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DataSourceCreator.java @@ -1,15 +1,12 @@ package org.springframework.cloud.service.relational; -import static org.springframework.cloud.service.Util.hasClass; - import java.sql.DriverManager; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; import javax.sql.DataSource; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.cloud.Cloud; import org.springframework.cloud.service.AbstractServiceConnectorCreator; import org.springframework.cloud.service.ServiceConnectorConfig; import org.springframework.cloud.service.ServiceConnectorCreationException; @@ -24,52 +21,41 @@ import org.springframework.jdbc.datasource.SimpleDriverDataSource; */ public abstract class DataSourceCreator extends AbstractServiceConnectorCreator { - private static Logger logger = Logger.getLogger(DataSourceCreator.class.getName()); - - private DataSourceConfigurer configurer = new DataSourceConfigurer(); + protected static Logger logger = Logger.getLogger(DataSourceCreator.class.getName()); public abstract String getDriverClassName(); public abstract String getValidationQuery(); + private List> pooledDataSourceCreators = new ArrayList>(); + + public DataSourceCreator() { + if (pooledDataSourceCreators.size() == 0) { + pooledDataSourceCreators.add(new BasicDbcpPooledDataSourceCreator()); + pooledDataSourceCreators.add(new TomcatDbcpPooledDataSourceCreator()); + pooledDataSourceCreators.add(new TomcatHighPerformancePooledDataSourceCreator()); + } + } + @Override public DataSource create(SI serviceInfo, ServiceConnectorConfig serviceConnectorConfig) { try { Class.forName(getDriverClassName()); - // Give first preference to user's DBCP datasource - if (hasClass("org.apache.commons.dbcp.BasicDataSource")) { - logger.info("Found DBCP on the classpath. Using it for DataSource connection pooling."); - org.apache.commons.dbcp.BasicDataSource ds = new org.apache.commons.dbcp.BasicDataSource(); - setBasicDataSourceProperties(ds, serviceInfo, serviceConnectorConfig); - return ds; - // else, we have one from Tomcat - } else if (hasClass("org.apache.tomcat.jdbc.pool.DataSource")) { - logger.info("Found Tomcat connection pool on the classpath. Using it for DataSource connection pooling."); - org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(); - setBasicDataSourceProperties(ds, serviceInfo, serviceConnectorConfig); - return ds; - } else { - // Only for testing outside Tomcat/CloudFoundry - logger.warning("Found neither DBCP nor Tomcat connection pool on the classpath (no pooling in effect)"); - return new SimpleDriverDataSource(DriverManager.getDriver(serviceInfo.getJdbcUrl()), - serviceInfo.getJdbcUrl()); + + for (PooledDataSourceCreator delegate: pooledDataSourceCreators) { + DataSource ds = delegate.create(serviceInfo, serviceConnectorConfig, getDriverClassName(), getValidationQuery()); + + if (ds != null) { + return ds; + } } + // Only for testing outside Tomcat/CloudFoundry + logger.warning("Found neither DBCP nor Tomcat connection pool on the classpath (no pooling is in effect)."); + return new SimpleDriverDataSource(DriverManager.getDriver(serviceInfo.getJdbcUrl()), serviceInfo.getJdbcUrl()); } catch (Exception e) { throw new ServiceConnectorCreationException( "Failed to created cloud datasource for " + serviceInfo.getId() + " service", e); } } - - private void setBasicDataSourceProperties(DataSource basicDataSource, RelationalServiceInfo serviceInfo, - ServiceConnectorConfig serviceConnectorConfig) { - BeanWrapper target = new BeanWrapperImpl(basicDataSource); - target.setPropertyValue("driverClassName", getDriverClassName()); - target.setPropertyValue("url", serviceInfo.getJdbcUrl()); - if (getValidationQuery() != null) { - target.setPropertyValue("validationQuery", getValidationQuery()); - target.setPropertyValue("testOnBorrow", true); - } - configurer.configure(basicDataSource, (DataSourceConfig)serviceConnectorConfig); - } } diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DbcpLikePooledDataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DbcpLikePooledDataSourceCreator.java new file mode 100644 index 0000000..e467bc6 --- /dev/null +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/DbcpLikePooledDataSourceCreator.java @@ -0,0 +1,38 @@ +package org.springframework.cloud.service.relational; + +import java.util.logging.Logger; + +import javax.sql.DataSource; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RelationalServiceInfo; + +/** + * Common implementation that assumes DBCP connection pool properties. + * + * @author Ramnivas Laddad + * + * @param + */ +public abstract class DbcpLikePooledDataSourceCreator implements PooledDataSourceCreator { + + protected static Logger logger = Logger.getLogger(PooledDataSourceCreator.class.getName()); + + private DataSourceConfigurer configurer = new DataSourceConfigurer(); + + protected void setBasicDataSourceProperties(DataSource basicDataSource, RelationalServiceInfo serviceInfo, + ServiceConnectorConfig serviceConnectorConfig, + String driverClassName, String validationQuery) { + BeanWrapper target = new BeanWrapperImpl(basicDataSource); + target.setPropertyValue("driverClassName", driverClassName); + target.setPropertyValue("url", serviceInfo.getJdbcUrl()); + if (validationQuery != null) { + target.setPropertyValue("validationQuery", validationQuery); + target.setPropertyValue("testOnBorrow", true); + } + configurer.configure(basicDataSource, (DataSourceConfig)serviceConnectorConfig); + } + +} diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/PooledDataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/PooledDataSourceCreator.java new file mode 100644 index 0000000..3f8ec83 --- /dev/null +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/PooledDataSourceCreator.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.service.relational; + +import javax.sql.DataSource; + +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RelationalServiceInfo; + +/** + * DataSource creator that produces a pooled connection + * + * @author Ramnivas Laddad + * + * @param + */ +public interface PooledDataSourceCreator { + public abstract DataSource create(RelationalServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfig, + String driverClassName, String validationQuery); +} + diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatDbcpPooledDataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatDbcpPooledDataSourceCreator.java new file mode 100644 index 0000000..74709e1 --- /dev/null +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatDbcpPooledDataSourceCreator.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.service.relational; + +import static org.springframework.cloud.service.Util.hasClass; + +import javax.sql.DataSource; + +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RelationalServiceInfo; + +/** + * + * @author Ramnivas Laddad + * + * @param + */ +public class TomcatDbcpPooledDataSourceCreator extends DbcpLikePooledDataSourceCreator { + + @Override + public DataSource create(RelationalServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfig, + String driverClassName, String validationQuery) { + if (hasClass("org.apache.tomcat.dbcp.dbcp.BasicDataSource")) { + logger.info("Found Tomcat dbcp connection pool on the classpath. Using it for DataSource connection pooling."); + org.apache.tomcat.dbcp.dbcp.BasicDataSource ds = new org.apache.tomcat.dbcp.dbcp.BasicDataSource(); + setBasicDataSourceProperties(ds, serviceInfo, serviceConnectorConfig, driverClassName, validationQuery); + return ds; + } else { + return null; + } + } + +} diff --git a/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatHighPerformancePooledDataSourceCreator.java b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatHighPerformancePooledDataSourceCreator.java new file mode 100644 index 0000000..7522417 --- /dev/null +++ b/spring-service-connector/src/main/java/org/springframework/cloud/service/relational/TomcatHighPerformancePooledDataSourceCreator.java @@ -0,0 +1,32 @@ +package org.springframework.cloud.service.relational; + +import static org.springframework.cloud.service.Util.hasClass; + +import javax.sql.DataSource; + +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RelationalServiceInfo; + +/** + * + * @author Ramnivas Laddad + * + * @param + */ +public class TomcatHighPerformancePooledDataSourceCreator + extends DbcpLikePooledDataSourceCreator { + + @Override + public DataSource create(RelationalServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfig, + String driverClassName, String validationQuery) { + if (hasClass("org.apache.tomcat.jdbc.pool.DataSource")) { + logger.info("Found Tomcat connection pool on the classpath. Using it for DataSource connection pooling."); + org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(); + setBasicDataSourceProperties(ds, serviceInfo, serviceConnectorConfig, driverClassName, validationQuery); + return ds; + } else { + return null; + } + } + +} diff --git a/spring-service-connector/src/test/java/org/springframework/cloud/service/relational/PooledDataSourceCreatorsTest.java b/spring-service-connector/src/test/java/org/springframework/cloud/service/relational/PooledDataSourceCreatorsTest.java new file mode 100644 index 0000000..f8fd726 --- /dev/null +++ b/spring-service-connector/src/test/java/org/springframework/cloud/service/relational/PooledDataSourceCreatorsTest.java @@ -0,0 +1,42 @@ +package org.springframework.cloud.service.relational; + +import java.util.ArrayList; +import java.util.List; + +import javax.sql.DataSource; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.cloud.service.common.MysqlServiceInfo; + +public class PooledDataSourceCreatorsTest { + @Mock private MysqlServiceInfo mockMysqlServiceInfo; + + // Just to grab driver class name and validation query string + private MysqlDataSourceCreator mysqlDataSourceCreator = new MysqlDataSourceCreator(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void pooledDataSourceCreation() { + List> pooledDataSourceCreators = new ArrayList>(); + + pooledDataSourceCreators.add(new BasicDbcpPooledDataSourceCreator()); + pooledDataSourceCreators.add(new TomcatDbcpPooledDataSourceCreator()); + pooledDataSourceCreators.add(new TomcatHighPerformancePooledDataSourceCreator()); + + for (PooledDataSourceCreator testCreator : pooledDataSourceCreators) { + DataSource ds = testCreator.create(mockMysqlServiceInfo, null, + mysqlDataSourceCreator.getDriverClassName(), + mysqlDataSourceCreator.getValidationQuery()); + + Assert.assertNotNull(ds); + } + } +}