CAY-2008 Connection pool refactoring and validation query support in Cayenne 
DataSource

* Refactoring and cleanup of DataSource implementation
* DataSourceBuilder
* splitting pool management thread into a separate DS wrapper
* implementing java.sql.Wrapper methods
* pool specific parameters are passed as a special object
* stop supporting getting connections with expclicit username/password. We do 
not have any
  notion of per-user connection pools, so it is only misleading and can lead
  to unpredictable pool state
* validate query


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/8b54c052
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/8b54c052
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/8b54c052

Branch: refs/heads/master
Commit: 8b54c052d6e54a5218c049819e35d718b4049af0
Parents: 5e037fc
Author: aadamchik <aadamc...@apache.org>
Authored: Thu Apr 30 13:34:21 2015 +0300
Committer: aadamchik <aadamc...@apache.org>
Committed: Thu Apr 30 18:46:25 2015 +0300

----------------------------------------------------------------------
 .../apache/cayenne/di/ScopeEventListener.java   |   19 +-
 .../server/PropertyDataSourceFactory.java       |   86 +-
 .../server/XMLPoolingDataSourceFactory.java     |   59 +-
 .../apache/cayenne/conn/DataSourceBuilder.java  |  135 ++
 .../org/apache/cayenne/conn/DataSourceInfo.java |   74 +-
 .../cayenne/conn/ManagedPoolingDataSource.java  |  155 ++
 .../org/apache/cayenne/conn/PoolDataSource.java |   90 -
 .../org/apache/cayenne/conn/PoolManager.java    |  649 --------
 .../cayenne/conn/PooledConnectionFactory.java   |   90 +
 .../cayenne/conn/PooledConnectionImpl.java      |  320 ++--
 .../apache/cayenne/conn/PoolingDataSource.java  |  471 ++++++
 .../conn/PoolingDataSourceParameters.java       |   64 +
 .../apache/cayenne/access/DataContextIT.java    | 1571 +++++++++---------
 .../cayenne/conn/BasePoolingDataSourceIT.java   |   74 +
 .../org/apache/cayenne/conn/PoolManagerIT.java  |  128 --
 .../cayenne/conn/PooledConnectionImplTest.java  |   68 +-
 .../cayenne/conn/PoolingDataSourceIT.java       |   72 +
 ...lingDataSource_FailingValidationQueryIT.java |   43 +
 .../PoolingDataSource_ValidationQueryIT.java    |   55 +
 .../di/server/ServerCaseDataSourceFactory.java  |   89 +-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |    1 +
 21 files changed, 2296 insertions(+), 2017 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
----------------------------------------------------------------------
diff --git 
a/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java 
b/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
index 171bd50..b6959ac 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
@@ -18,18 +18,19 @@
  ****************************************************************/
 package org.apache.cayenne.di;
 
-
 /**
- * This interface duplicates default reflection based mechanism for receiving 
DI events.
- * It is not fully supported and its usage are reserved for cases when for 
some reason
- * it is not possible to use reflection. It is used for example in
- * {@link javax.sql.DataSource} managing layer to provide compatibility with 
java version 5.
+ * This interface duplicates default reflection based mechanism for receiving 
DI
+ * events. It is not fully supported and its usage are reserved for cases when
+ * for some reason it is not possible to use reflection. It is used for example
+ * in {@link javax.sql.DataSource} management layer to provide compatibility
+ * with java version 5.
  *
  * @since 3.1
  */
 public interface ScopeEventListener {
-    /**
-     * Similar to {@link BeforeScopeEnd}
-     */
-    void beforeScopeEnd();
+       
+       /**
+        * Similar to {@link BeforeScopeEnd}
+        */
+       void beforeScopeEnd();
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
index 237f4e4..87d18de 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
@@ -18,17 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
-import java.sql.Driver;
-
 import javax.sql.DataSource;
 
 import org.apache.cayenne.ConfigurationException;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.RuntimeProperties;
-import org.apache.cayenne.conn.DriverDataSource;
-import org.apache.cayenne.conn.PoolDataSource;
-import org.apache.cayenne.conn.PoolManager;
+import org.apache.cayenne.conn.DataSourceBuilder;
+import org.apache.cayenne.conn.PoolingDataSource;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -52,57 +49,50 @@ import org.apache.cayenne.log.JdbcEventLogger;
  */
 public class PropertyDataSourceFactory implements DataSourceFactory {
 
-    @Inject
-    protected RuntimeProperties properties;
-
-    @Inject
-    protected JdbcEventLogger jdbcEventLogger;
+       @Inject
+       protected RuntimeProperties properties;
 
-    @Inject
-    private AdhocObjectFactory objectFactory;
+       @Inject
+       protected JdbcEventLogger jdbcEventLogger;
 
-    @Override
-    public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws 
Exception {
+       @Inject
+       private AdhocObjectFactory objectFactory;
 
-        String suffix = "." + 
nodeDescriptor.getDataChannelDescriptor().getName() + "." + 
nodeDescriptor.getName();
+       @Override
+       public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) 
throws Exception {
 
-        String driverClass = getProperty(Constants.JDBC_DRIVER_PROPERTY, 
suffix);
-        String url = getProperty(Constants.JDBC_URL_PROPERTY, suffix);
-        String username = getProperty(Constants.JDBC_USERNAME_PROPERTY, 
suffix);
-        String password = getProperty(Constants.JDBC_PASSWORD_PROPERTY, 
suffix);
-        int minConnections = 
getIntProperty(Constants.JDBC_MIN_CONNECTIONS_PROPERTY, suffix, 1);
-        int maxConnections = 
getIntProperty(Constants.JDBC_MAX_CONNECTIONS_PROPERTY, suffix, 1);
+               String suffix = "." + 
nodeDescriptor.getDataChannelDescriptor().getName() + "." + 
nodeDescriptor.getName();
 
-        Driver driver = objectFactory.newInstance(Driver.class, driverClass);
-        DriverDataSource driverDS = new DriverDataSource(driver, url, 
username, password);
-        driverDS.setLogger(jdbcEventLogger);
-        PoolDataSource poolDS = new PoolDataSource(driverDS);
+               String driverClass = 
getProperty(Constants.JDBC_DRIVER_PROPERTY, suffix);
+               String url = getProperty(Constants.JDBC_URL_PROPERTY, suffix);
+               String username = getProperty(Constants.JDBC_USERNAME_PROPERTY, 
suffix);
+               String password = getProperty(Constants.JDBC_PASSWORD_PROPERTY, 
suffix);
+               int minConnections = 
getIntProperty(Constants.JDBC_MIN_CONNECTIONS_PROPERTY, suffix, 1);
+               int maxConnections = 
getIntProperty(Constants.JDBC_MAX_CONNECTIONS_PROPERTY, suffix, 1);
+               long maxQueueWaitTime = 
properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
+                               PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
 
-        try {
-            return new PoolManager(poolDS, minConnections, maxConnections, 
username, password, properties.getLong(
-                    Constants.SERVER_MAX_QUEUE_WAIT_TIME, 
PoolManager.MAX_QUEUE_WAIT_DEFAULT));
-        } catch (Exception e) {
-            jdbcEventLogger.logConnectFailure(e);
-            throw e;
-        }
-    }
+               return DataSourceBuilder.builder(objectFactory, 
jdbcEventLogger).driver(driverClass).url(url)
+                               
.userName(username).password(password).minConnections(minConnections).maxConnections(maxConnections)
+                               .maxQueueWaitTime(maxQueueWaitTime).build();
+       }
 
-    protected int getIntProperty(String propertyName, String suffix, int 
defaultValue) {
-        String string = getProperty(propertyName, suffix);
+       protected int getIntProperty(String propertyName, String suffix, int 
defaultValue) {
+               String string = getProperty(propertyName, suffix);
 
-        if (string == null) {
-            return defaultValue;
-        }
+               if (string == null) {
+                       return defaultValue;
+               }
 
-        try {
-            return Integer.parseInt(string);
-        } catch (NumberFormatException e) {
-            throw new ConfigurationException("Invalid int property '%s': 
'%s'", propertyName, string);
-        }
-    }
+               try {
+                       return Integer.parseInt(string);
+               } catch (NumberFormatException e) {
+                       throw new ConfigurationException("Invalid int property 
'%s': '%s'", propertyName, string);
+               }
+       }
 
-    protected String getProperty(String propertyName, String suffix) {
-        String value = properties.get(propertyName + suffix);
-        return value != null ? value : properties.get(propertyName);
-    }
+       protected String getProperty(String propertyName, String suffix) {
+               String value = properties.get(propertyName + suffix);
+               return value != null ? value : properties.get(propertyName);
+       }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
index b3bad8d..a62a2d9 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
@@ -18,18 +18,15 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
-import java.sql.Driver;
-
 import javax.sql.DataSource;
 
 import org.apache.cayenne.ConfigurationException;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.conn.DataSourceBuilder;
 import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.DriverDataSource;
-import org.apache.cayenne.conn.PoolDataSource;
-import org.apache.cayenne.conn.PoolManager;
+import org.apache.cayenne.conn.PoolingDataSource;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -47,43 +44,35 @@ import org.apache.commons.logging.LogFactory;
 // something else?
 public class XMLPoolingDataSourceFactory implements DataSourceFactory {
 
-    private static final Log logger = 
LogFactory.getLog(XMLPoolingDataSourceFactory.class);
+       private static final Log logger = 
LogFactory.getLog(XMLPoolingDataSourceFactory.class);
 
-    @Inject
-    protected JdbcEventLogger jdbcEventLogger;
+       @Inject
+       protected JdbcEventLogger jdbcEventLogger;
 
-    @Inject
-    private RuntimeProperties properties;
+       @Inject
+       private RuntimeProperties properties;
 
-    @Inject
-    private AdhocObjectFactory objectFactory;
+       @Inject
+       private AdhocObjectFactory objectFactory;
 
-    @Override
-    public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws 
Exception {
+       @Override
+       public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) 
throws Exception {
 
-        DataSourceInfo dataSourceDescriptor = 
nodeDescriptor.getDataSourceDescriptor();
+               DataSourceInfo descriptor = 
nodeDescriptor.getDataSourceDescriptor();
 
-        if (dataSourceDescriptor == null) {
-            String message = "Null dataSourceDescriptor for nodeDescriptor '" 
+ nodeDescriptor.getName() + "'";
-            logger.info(message);
-            throw new ConfigurationException(message);
-        }
+               if (descriptor == null) {
+                       String message = "Null dataSourceDescriptor for 
nodeDescriptor '" + nodeDescriptor.getName() + "'";
+                       logger.info(message);
+                       throw new ConfigurationException(message);
+               }
 
-        Driver driver = objectFactory.newInstance(Driver.class, 
dataSourceDescriptor.getJdbcDriver());
-        DriverDataSource driverDS = new DriverDataSource(driver, 
dataSourceDescriptor.getDataSourceUrl(),
-                dataSourceDescriptor.getUserName(), 
dataSourceDescriptor.getPassword());
-        driverDS.setLogger(jdbcEventLogger);
-        PoolDataSource poolDS = new PoolDataSource(driverDS);
+               long maxQueueWaitTime = 
properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
+                               PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
 
-        try {
-            return new PoolManager(poolDS, 
dataSourceDescriptor.getMinConnections(),
-                    dataSourceDescriptor.getMaxConnections(), 
dataSourceDescriptor.getUserName(),
-                    dataSourceDescriptor.getPassword(), 
properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
-                            PoolManager.MAX_QUEUE_WAIT_DEFAULT));
-        } catch (Exception e) {
-            jdbcEventLogger.logConnectFailure(e);
-            throw e;
-        }
-    }
+               return DataSourceBuilder.builder(objectFactory, 
jdbcEventLogger).driver(descriptor.getJdbcDriver())
+                               
.url(descriptor.getDataSourceUrl()).userName(descriptor.getUserName())
+                               
.password(descriptor.getPassword()).minConnections(descriptor.getMinConnections())
+                               
.maxConnections(descriptor.getMaxConnections()).maxQueueWaitTime(maxQueueWaitTime).build();
+       }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
new file mode 100644
index 0000000..c850d64
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
@@ -0,0 +1,135 @@
+/*****************************************************************
+ *   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.cayenne.conn;
+
+import java.sql.Driver;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.log.NoopJdbcEventLogger;
+
+/**
+ * A builder class that creates a default Cayenne implementation of a pooling
+ * {@link DataSource}.
+ * 
+ * @since 4.0
+ */
+public class DataSourceBuilder {
+
+       private AdhocObjectFactory objectFactory;
+       private JdbcEventLogger logger;
+       private String userName;
+       private String password;
+       private String driver;
+       private String url;
+       private PoolingDataSourceParameters poolParameters;
+
+       public static DataSourceBuilder builder(AdhocObjectFactory 
objectFactory, JdbcEventLogger logger) {
+               return new DataSourceBuilder(objectFactory, logger);
+       }
+
+       private DataSourceBuilder(AdhocObjectFactory objectFactory, 
JdbcEventLogger logger) {
+               this.objectFactory = objectFactory;
+               this.logger = logger;
+               this.logger = NoopJdbcEventLogger.getInstance();
+               this.poolParameters = new PoolingDataSourceParameters();
+
+               poolParameters.setMinConnections(1);
+               poolParameters.setMaxConnections(1);
+               
poolParameters.setMaxQueueWaitTime(PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
+       }
+
+       public DataSourceBuilder userName(String userName) {
+               this.userName = userName;
+               return this;
+       }
+
+       public DataSourceBuilder password(String password) {
+               this.password = password;
+               return this;
+       }
+
+       public DataSourceBuilder driver(String driver) {
+               this.driver = driver;
+               return this;
+       }
+
+       public DataSourceBuilder url(String url) {
+               this.url = url;
+               return this;
+       }
+
+       public DataSourceBuilder minConnections(int minConnections) {
+               poolParameters.setMinConnections(minConnections);
+               return this;
+       }
+
+       public DataSourceBuilder maxConnections(int maxConnections) {
+               poolParameters.setMaxConnections(maxConnections);
+               return this;
+       }
+
+       public DataSourceBuilder maxQueueWaitTime(long maxQueueWaitTime) {
+               poolParameters.setMaxQueueWaitTime(maxQueueWaitTime);
+               return this;
+       }
+
+       public DataSource build() {
+
+               // sanity checks...
+               if (poolParameters.getMaxConnections() < 0) {
+                       throw new CayenneRuntimeException("Maximum number of 
connections can not be negative ("
+                                       + poolParameters.getMaxConnections() + 
").");
+               }
+
+               if (poolParameters.getMinConnections() < 0) {
+                       throw new CayenneRuntimeException("Minimum number of 
connections can not be negative ("
+                                       + poolParameters.getMinConnections() + 
").");
+               }
+
+               if (poolParameters.getMinConnections() > 
poolParameters.getMaxConnections()) {
+                       throw new CayenneRuntimeException("Minimum number of 
connections can not be bigger then maximum.");
+               }
+
+               DataSource nonPooling = buildNonPoolingDataSource();
+               return buildPoolingDataSource(new 
PooledConnectionFactory(nonPooling));
+       }
+
+       private DataSource buildNonPoolingDataSource() {
+               Driver driver = objectFactory.newInstance(Driver.class, 
this.driver);
+               DriverDataSource dataSource = new DriverDataSource(driver, url, 
userName, password);
+               dataSource.setLogger(logger);
+               return dataSource;
+       }
+
+       private DataSource buildPoolingDataSource(PooledConnectionFactory 
connectionFactory) {
+               PoolingDataSource poolDS;
+               try {
+                       poolDS = new PoolingDataSource(connectionFactory, 
poolParameters);
+               } catch (Exception e) {
+                       logger.logConnectFailure(e);
+                       throw new CayenneRuntimeException("Error creating 
DataSource", e);
+               }
+
+               return new ManagedPoolingDataSource(poolDS);
+       }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
index 44c95c1..2bb2664 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
@@ -39,6 +39,11 @@ public class DataSourceInfo implements Cloneable, 
Serializable, XMLSerializable
 
        private static Log logger = LogFactory.getLog(DataSourceInfo.class);
 
+       public static final String PASSWORD_LOCATION_CLASSPATH = "classpath";
+       public static final String PASSWORD_LOCATION_EXECUTABLE = "executable";
+       public static final String PASSWORD_LOCATION_MODEL = "model";
+       public static final String PASSWORD_LOCATION_URL = "url";
+
        protected String userName;
        protected String password;
        protected String jdbcDriver;
@@ -46,14 +51,6 @@ public class DataSourceInfo implements Cloneable, 
Serializable, XMLSerializable
        protected String adapterClassName;
        protected int minConnections = 1;
        protected int maxConnections = 1;
-
-       // Constants for passwordLocation
-       public static final String PASSWORD_LOCATION_CLASSPATH = "classpath";
-       public static final String PASSWORD_LOCATION_EXECUTABLE = "executable";
-       public static final String PASSWORD_LOCATION_MODEL = "model";
-       public static final String PASSWORD_LOCATION_URL = "url";
-
-       // Extended parameters
        protected String passwordEncoderClass = 
PlainTextPasswordEncoder.class.getName();
        protected String passwordEncoderKey = "";
        protected String passwordLocation = PASSWORD_LOCATION_MODEL;
@@ -64,43 +61,72 @@ public class DataSourceInfo implements Cloneable, 
Serializable, XMLSerializable
 
        @Override
        public boolean equals(Object obj) {
-               if (obj == this)
+               
+               if (obj == this) {
                        return true;
+               }
 
-               if (obj == null)
+               if (obj == null) {
                        return false;
+               }
 
-               if (obj.getClass() != this.getClass())
+               if (obj.getClass() != this.getClass()) {
                        return false;
+               }
 
                DataSourceInfo dsi = (DataSourceInfo) obj;
 
-               if (!Util.nullSafeEquals(this.userName, dsi.userName))
+               if (!Util.nullSafeEquals(this.userName, dsi.userName)) {
                        return false;
-               if (!Util.nullSafeEquals(this.password, dsi.password))
+               }
+
+               if (!Util.nullSafeEquals(this.password, dsi.password)) {
                        return false;
-               if (!Util.nullSafeEquals(this.jdbcDriver, dsi.jdbcDriver))
+               }
+
+               if (!Util.nullSafeEquals(this.jdbcDriver, dsi.jdbcDriver)) {
                        return false;
-               if (!Util.nullSafeEquals(this.dataSourceUrl, dsi.dataSourceUrl))
+               }
+
+               if (!Util.nullSafeEquals(this.dataSourceUrl, 
dsi.dataSourceUrl)) {
                        return false;
-               if (!Util.nullSafeEquals(this.adapterClassName, 
dsi.adapterClassName))
+               }
+
+               if (!Util.nullSafeEquals(this.adapterClassName, 
dsi.adapterClassName)) {
                        return false;
-               if (this.minConnections != dsi.minConnections)
+               }
+
+               if (this.minConnections != dsi.minConnections) {
                        return false;
-               if (this.maxConnections != dsi.maxConnections)
+               }
+
+               if (this.maxConnections != dsi.maxConnections) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordEncoderClass, 
dsi.passwordEncoderClass))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordEncoderClass, 
dsi.passwordEncoderClass)) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordEncoderKey, 
dsi.passwordEncoderKey))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordEncoderKey, 
dsi.passwordEncoderKey)) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordSourceFilename, 
dsi.passwordSourceFilename))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordSourceFilename, 
dsi.passwordSourceFilename)) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordSourceModel, 
dsi.passwordSourceModel))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordSourceModel, 
dsi.passwordSourceModel)) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordSourceUrl, 
dsi.passwordSourceUrl))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordSourceUrl, 
dsi.passwordSourceUrl)) {
                        return false;
-               if (!Util.nullSafeEquals(this.passwordLocation, 
dsi.passwordLocation))
+               }
+
+               if (!Util.nullSafeEquals(this.passwordLocation, 
dsi.passwordLocation)) {
                        return false;
+               }
 
                return true;
        }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
new file mode 100644
index 0000000..357c3e0
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
@@ -0,0 +1,155 @@
+/*****************************************************************
+ *   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.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.di.ScopeEventListener;
+
+/**
+ * A wrapper for {@link PoolingDataSourceManager} that manages the underlying
+ * connection pool size, shrinking it if needed.
+ * 
+ * @since 4.0
+ */
+public class ManagedPoolingDataSource implements DataSource, 
ScopeEventListener {
+
+       private PoolingDataSourceManager dataSourceManager;
+       private PoolingDataSource dataSource;
+
+       public ManagedPoolingDataSource(PoolingDataSource dataSource) {
+
+               this.dataSource = dataSource;
+               this.dataSourceManager = new PoolingDataSourceManager();
+
+               dataSourceManager.start();
+       }
+
+       @Override
+       public void beforeScopeEnd() {
+               dataSourceManager.shouldStop();
+       }
+
+       @Override
+       public Connection getConnection() throws SQLException {
+               return dataSource.getConnection();
+       }
+
+       @Override
+       public Connection getConnection(String username, String password) 
throws SQLException {
+               return dataSource.getConnection(username, password);
+       }
+
+       @Override
+       public PrintWriter getLogWriter() throws SQLException {
+               return dataSource.getLogWriter();
+       }
+
+       @Override
+       public int getLoginTimeout() throws SQLException {
+               return dataSource.getLoginTimeout();
+       }
+
+       @Override
+       public boolean isWrapperFor(Class<?> iface) throws SQLException {
+               return (ManagedPoolingDataSource.class.equals(iface)) ? true : 
dataSource.isWrapperFor(iface);
+       }
+
+       @Override
+       public void setLogWriter(PrintWriter arg0) throws SQLException {
+               dataSource.setLogWriter(arg0);
+       }
+
+       @Override
+       public void setLoginTimeout(int arg0) throws SQLException {
+               dataSource.setLoginTimeout(arg0);
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> T unwrap(Class<T> iface) throws SQLException {
+               return ManagedPoolingDataSource.class.equals(iface) ? (T) this 
: dataSource.unwrap(iface);
+       }
+
+       // JDBC 4.1 compatibility under Java 1.6 and newer
+       public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+               throw new UnsupportedOperationException();
+       }
+
+       boolean shouldShrinkPool() {
+               int unused = dataSource.getCurrentlyUnused();
+               int used = dataSource.getCurrentlyInUse();
+               int total = unused + used;
+               int median = dataSource.getMinConnections() + 1
+                               + (dataSource.getMaxConnections() - 
dataSource.getMinConnections()) / 2;
+
+               return unused > 0 && total > median;
+       }
+
+       class PoolingDataSourceManager extends Thread {
+
+               private volatile boolean shouldStop;
+
+               PoolingDataSourceManager() {
+                       setName("PoolManagerCleanup-" + dataSource.hashCode());
+                       setDaemon(true);
+                       this.shouldStop = false;
+               }
+
+               public void shouldStop() {
+                       shouldStop = true;
+                       interrupt();
+               }
+
+               @Override
+               public void run() {
+                       while (true) {
+
+                               try {
+                                       // don't do it too often
+                                       Thread.sleep(600000);
+                               } catch (InterruptedException iex) {
+                                       // ignore...
+                               }
+
+                               synchronized (dataSource) {
+
+                                       // simple pool management - close one 
connection if the
+                                       // count is
+                                       // above median and there are any idle 
connections.
+
+                                       if (shouldStop) {
+                                               break;
+                                       }
+
+                                       if (shouldShrinkPool()) {
+                                               dataSource.shrinkPool(1);
+                                       }
+                               }
+                       }
+               }
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
deleted file mode 100644
index ad0b33e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*****************************************************************
- *   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.cayenne.conn;
-
-import java.io.PrintWriter;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.logging.Logger;
-
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-import javax.sql.PooledConnection;
-
-/**
- * PoolDataSource allows to generate pooled connections.
- *
- * <p>
- * It is implemented as a wrapper around a non-pooled data source object.
- * Delegates all method calls except for "getPooledConnection" to the 
underlying
- * DataSource.
- * 
- */
-public class PoolDataSource implements ConnectionPoolDataSource {
-
-       private DataSource nonPooledDatasource;
-
-       public PoolDataSource(DataSource nonPooledDatasource) {
-               this.nonPooledDatasource = nonPooledDatasource;
-       }
-
-       public PoolDataSource(String jdbcDriver, String connectionUrl) throws 
SQLException {
-               nonPooledDatasource = new DriverDataSource(jdbcDriver, 
connectionUrl);
-       }
-
-       @Override
-       public int getLoginTimeout() throws SQLException {
-               return nonPooledDatasource.getLoginTimeout();
-       }
-
-       @Override
-       public void setLoginTimeout(int seconds) throws SQLException {
-               nonPooledDatasource.setLoginTimeout(seconds);
-       }
-
-       @Override
-       public PrintWriter getLogWriter() throws SQLException {
-               return nonPooledDatasource.getLogWriter();
-       }
-
-       @Override
-       public void setLogWriter(PrintWriter out) throws SQLException {
-               nonPooledDatasource.setLogWriter(out);
-       }
-
-       @Override
-       public PooledConnection getPooledConnection() throws SQLException {
-               return new PooledConnectionImpl(nonPooledDatasource, null, 
null);
-       }
-
-       @Override
-       public PooledConnection getPooledConnection(String user, String 
password) throws SQLException {
-               return new PooledConnectionImpl(nonPooledDatasource, user, 
password);
-       }
-
-       /**
-        * @since 3.1
-        *
-        *        JDBC 4.1 compatibility under Java 1.7
-        */
-       public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-               throw new UnsupportedOperationException();
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
deleted file mode 100644
index 1a71024..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
+++ /dev/null
@@ -1,649 +0,0 @@
-/*****************************************************************
- *   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.cayenne.conn;
-
-import java.io.PrintWriter;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.logging.Logger;
-
-import javax.sql.ConnectionEvent;
-import javax.sql.ConnectionEventListener;
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-import javax.sql.PooledConnection;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.di.ScopeEventListener;
-import org.apache.cayenne.log.JdbcEventLogger;
-
-/**
- * PoolManager is a Cayenne implementation of a pooling DataSource.
- */
-public class PoolManager implements ScopeEventListener, DataSource,
-        ConnectionEventListener {
-
-    /**
-     * Defines a maximum time in milliseconds that a connection request could 
wait in the
-     * connection queue. After this period expires, an exception will be 
thrown in the
-     * calling method. 
-     */
-    public static final int MAX_QUEUE_WAIT_DEFAULT = 20000;
-
-    /**
-     * An exception indicating that a connection request waiting in the queue
-     * timed out and was unable to obtain a connection.
-     */
-    public static class ConnectionUnavailableException extends SQLException {
-        private static final long serialVersionUID = 1063973806941023165L;
-
-        public ConnectionUnavailableException(String message) {
-               super(message);
-       }
-    }
-    
-    protected ConnectionPoolDataSource poolDataSource;
-    protected int minConnections;
-    protected int maxConnections;
-    protected String dataSourceUrl;
-    protected String jdbcDriver;
-    protected String password;
-    protected String userName;
-
-    protected List<PooledConnection> unusedPool;
-    protected List<PooledConnection> usedPool;
-
-    private PoolMaintenanceThread poolMaintenanceThread;
-
-    private boolean shuttingDown;
-    private long maxQueueWaitTime;
-    
-    /**
-     * Creates new PoolManager using org.apache.cayenne.conn.PoolDataSource 
for an
-     * underlying ConnectionPoolDataSource.
-     * 
-     * @deprecated since 4.0 This constructor causes implicit class loading 
that should avoided.
-     */
-    @Deprecated
-    public PoolManager(String jdbcDriver, String dataSourceUrl, int minCons, 
int maxCons,
-            String userName, String password) throws SQLException {
-
-        this(jdbcDriver, dataSourceUrl, minCons, maxCons, userName, password, 
null, MAX_QUEUE_WAIT_DEFAULT);
-    }
-
-    /**
-     * @deprecated since 4.0 This constructor causes implicit class loading 
that should avoided.
-     */
-    @Deprecated
-    public PoolManager(String jdbcDriver, String dataSourceUrl, int minCons, 
int maxCons,
-            String userName, String password, JdbcEventLogger logger, long 
maxQueueWaitTime) throws SQLException {
-
-        if (logger != null) {
-            DataSourceInfo info = new DataSourceInfo();
-            info.setJdbcDriver(jdbcDriver);
-            info.setDataSourceUrl(dataSourceUrl);
-            info.setMinConnections(minCons);
-            info.setMaxConnections(maxCons);
-            info.setUserName(userName);
-            info.setPassword(password);
-            logger.logPoolCreated(info);
-        }
-
-        this.jdbcDriver = jdbcDriver;
-        this.dataSourceUrl = dataSourceUrl;
-        DriverDataSource driverDS = new DriverDataSource(jdbcDriver, 
dataSourceUrl);
-        driverDS.setLogger(logger);
-        PoolDataSource poolDS = new PoolDataSource(driverDS);
-        init(poolDS, minCons, maxCons, userName, password, maxQueueWaitTime);
-    }
-    
-    /**
-     * Creates new PoolManager with the specified policy for connection 
pooling and a
-     * ConnectionPoolDataSource object.
-     * 
-     * @param poolDataSource data source for pooled connections
-     * @param minCons Non-negative integer that specifies a minimum number of 
open
-     *            connections to keep in the pool at all times
-     * @param maxCons Non-negative integer that specifies maximum number of 
simultaneuosly
-     *            open connections
-     * @throws SQLException if pool manager can not be created.
-     * @deprecated since 4.0 use {@link #PoolManager(ConnectionPoolDataSource, 
int, int, String, String, long)}
-     */
-    public PoolManager(ConnectionPoolDataSource poolDataSource, int minCons, 
int maxCons,
-            String userName, String password) throws SQLException {
-        this(poolDataSource, minCons, maxCons, userName, password, 
PoolManager.MAX_QUEUE_WAIT_DEFAULT);
-    }
-
-    /**
-     * Creates new PoolManager with the specified policy for connection 
pooling and a
-     * ConnectionPoolDataSource object.
-     * 
-     * @param poolDataSource data source for pooled connections
-     * @param minCons Non-negative integer that specifies a minimum number of 
open
-     *            connections to keep in the pool at all times
-     * @param maxCons Non-negative integer that specifies maximum number of 
simultaneuosly
-     *            open connections
-     * @throws SQLException if pool manager can not be created.
-     * @since 4.0
-     */
-    public PoolManager(ConnectionPoolDataSource poolDataSource, int minCons, 
int maxCons,
-            String userName, String password, long maxQueueWaitTime) throws 
SQLException {
-        init(poolDataSource, minCons, maxCons, userName, password, 
maxQueueWaitTime);
-    }
-
-    /** Initializes pool. Normally called from constructor. */
-    protected void init(
-            ConnectionPoolDataSource poolDataSource,
-            int minCons,
-            int maxCons,
-            String userName,
-            String password,
-            long maxQueueWaitTime) throws SQLException {
-
-        // do sanity checks...
-        if (maxConnections < 0) {
-            throw new SQLException("Maximum number of connections can not be 
negative ("
-                    + maxCons
-                    + ").");
-        }
-
-        if (minConnections < 0) {
-            throw new SQLException("Minimum number of connections can not be 
negative ("
-                    + minCons
-                    + ").");
-        }
-
-        if (minConnections > maxConnections) {
-            throw new SQLException(
-                    "Minimum number of connections can not be bigger then 
maximum.");
-        }
-
-        // init properties
-        this.userName = userName;
-        this.password = password;
-        this.minConnections = minCons;
-        this.maxConnections = maxCons;
-        this.poolDataSource = poolDataSource;
-        this.maxQueueWaitTime = maxQueueWaitTime;
-        
-        // init pool... use linked lists to use the queue in the FIFO manner
-        usedPool = new LinkedList<PooledConnection>();
-        unusedPool = new LinkedList<PooledConnection>();
-        growPool(minConnections, userName, password);
-
-        startMaintenanceThread();
-    }
-
-    protected synchronized void startMaintenanceThread() {
-        disposeOfMaintenanceThread();
-        this.poolMaintenanceThread = new PoolMaintenanceThread(this);
-        this.poolMaintenanceThread.start();
-    }
-
-    /**
-     * Creates and returns new PooledConnection object, adding itself as a 
listener for
-     * connection events.
-     */
-    protected PooledConnection newPooledConnection(String userName, String 
password)
-            throws SQLException {
-        PooledConnection connection = (userName != null) ? poolDataSource
-                .getPooledConnection(userName, password) : poolDataSource
-                .getPooledConnection();
-        connection.addConnectionEventListener(this);
-        return connection;
-    }
-
-    /**
-     * Closes all existing connections, drains the pool and stops the 
maintenance thread.
-     * 
-     * @since 3.1
-     */
-    public synchronized void shutdown() throws SQLException {
-
-        // disposing maintenance thread first to avoid any changes to pools
-        // during shutdown
-        disposeOfMaintenanceThread();
-
-        // using boolean variable instead of locking PoolManager instance due 
to
-        // possible deadlock during shutdown when one of connections locks its
-        // event listeners list trying to invoke locked PoolManager's listener 
methods
-        shuttingDown = true;
-
-        ListIterator<PooledConnection> unusedIterator = 
unusedPool.listIterator();
-        while (unusedIterator.hasNext()) {
-            PooledConnection con = unusedIterator.next();
-            // close connection
-            con.close();
-            // remove connection from the list
-            unusedIterator.remove();
-        }
-
-        // clean used connections
-        ListIterator<PooledConnection> usedIterator = usedPool.listIterator();
-        while (usedIterator.hasNext()) {
-            PooledConnection con = usedIterator.next();
-            // stop listening for connection events
-            con.removeConnectionEventListener(this);
-            // close connection
-            con.close();
-            // remove connection from the list
-            usedIterator.remove();
-        }
-    }
-
-    /**
-     * An implementation of {@link ScopeEventListener} that simply calls
-     * {@link #shutdown()}.
-     * 
-     * @since 3.1
-     */
-    public void beforeScopeEnd() {
-        try {
-            shutdown();
-        }
-        catch (SQLException e) {
-            throw new CayenneRuntimeException("Error while shutting down");
-        }
-    }
-
-    protected void disposeOfMaintenanceThread() {
-        if (poolMaintenanceThread != null) {
-            poolMaintenanceThread.shutdown();
-            poolMaintenanceThread = null;
-        }
-    }
-
-    /**
-     * @return true if at least one more connection can be added to the pool.
-     */
-    protected synchronized boolean canGrowPool() {
-        return getPoolSize() < maxConnections;
-    }
-
-    /**
-     * Increases connection pool by the specified number of connections.
-     * 
-     * @return the actual number of created connections.
-     * @throws SQLException if an error happens when creating a new connection.
-     */
-    protected synchronized int growPool(
-            int addConnections,
-            String userName,
-            String password) throws SQLException {
-
-        int i = 0;
-        int startPoolSize = getPoolSize();
-        for (; i < addConnections && startPoolSize + i < maxConnections; i++) {
-            PooledConnection newConnection = newPooledConnection(userName, 
password);
-            unusedPool.add(newConnection);
-        }
-
-        return i;
-    }
-
-    protected synchronized void shrinkPool(int closeConnections) {
-        int idleSize = unusedPool.size();
-        for (int i = 0; i < closeConnections && i < idleSize; i++) {
-            PooledConnection con = unusedPool.remove(i);
-
-            try {
-                con.close();
-            }
-            catch (SQLException ex) {
-                // ignore
-            }
-        }
-    }
-
-    /**
-     * Returns maximum number of connections this pool can keep. This 
parameter when
-     * configured allows to limit the number of simultaneously open 
connections.
-     */
-    public int getMaxConnections() {
-        return maxConnections;
-    }
-
-    public void setMaxConnections(int maxConnections) {
-        this.maxConnections = maxConnections;
-    }
-
-    /**
-     * Returns the absolute minimum number of connections allowed in this pool 
at any
-     * moment in time.
-     */
-    public int getMinConnections() {
-        return minConnections;
-    }
-
-    public void setMinConnections(int minConnections) {
-        this.minConnections = minConnections;
-    }
-
-    /**
-     * Returns a database URL used to initialize this pool. Will return null 
if the pool
-     * was initialized with ConnectionPoolDataSource.
-     */
-    public String getDataSourceUrl() {
-        return dataSourceUrl;
-    }
-
-    /**
-     * Returns a name of a JDBC driver used to initialize this pool. Will 
return null if
-     * the pool was initialized with ConnectionPoolDataSource.
-     */
-    public String getJdbcDriver() {
-        return jdbcDriver;
-    }
-
-    /** Returns a data source password used to initialize this pool. */
-    public String getPassword() {
-        return password;
-    }
-
-    /** Returns a data source user name used to initialize this pool. */
-    public String getUserName() {
-        return userName;
-    }
-
-    /**
-     * Returns current number of connections.
-     */
-    public synchronized int getPoolSize() {
-        return usedPool.size() + unusedPool.size();
-    }
-
-    /**
-     * Returns the number of connections obtained via this DataSource that are 
currently
-     * in use by the DataSource clients.
-     */
-    public synchronized int getCurrentlyInUse() {
-        return usedPool.size();
-    }
-
-    /**
-     * Returns the number of connections maintained in the pool that are 
currently not
-     * used by any clients and are available immediately via 
<code>getConnection</code>
-     * method.
-     */
-    public synchronized int getCurrentlyUnused() {
-        return unusedPool.size();
-    }
-
-    /**
-     * Returns connection from the pool using internal values of user name and 
password.
-     * Equivalent to calling:
-     * <p>
-     * <code>ds.getConnection(ds.getUserName(), ds.getPassword())</code>
-     * </p>
-     */
-    public Connection getConnection() throws SQLException {
-        return getConnection(userName, password);
-    }
-
-    /** Returns connection from the pool. */
-    public synchronized Connection getConnection(String userName, String 
password)
-            throws SQLException {
-
-        if (shuttingDown) {
-            throw new SQLException("Pool manager is shutting down.");
-        }
-
-        PooledConnection pooledConnection = uncheckPooledConnection(userName, 
password);
-
-        try {
-            return uncheckConnection(pooledConnection);
-        }
-        catch (SQLException ex) {
-
-            try {
-                pooledConnection.close();
-            }
-            catch (SQLException ignored) {
-            }
-
-            // do one reconnect attempt...
-            pooledConnection = uncheckPooledConnection(userName, password);
-            try {
-                return uncheckConnection(pooledConnection);
-            }
-            catch (SQLException reconnectEx) {
-                try {
-                    pooledConnection.close();
-                }
-                catch (SQLException ignored) {
-                }
-
-                throw reconnectEx;
-            }
-        }
-    }
-
-    private Connection uncheckConnection(PooledConnection pooledConnection)
-            throws SQLException {
-        Connection c = pooledConnection.getConnection();
-
-        // only do that on successfully unchecked connection...
-        usedPool.add(pooledConnection);
-        return c;
-    }
-
-    private PooledConnection uncheckPooledConnection(String userName, String 
password)
-            throws SQLException {
-        // wait for returned connections or the maintenance thread
-        // to bump the pool size...
-
-        if (unusedPool.size() == 0) {
-
-            // first try to open a new connection
-            if (canGrowPool()) {
-                return newPooledConnection(userName, password);
-            }
-
-            // can't open no more... will have to wait for others to return a 
connection
-
-            // note that if we were woken up
-            // before the full wait period expired, and no connections are
-            // available yet, go back to sleep. Otherwise we don't give a 
maintenance
-            // thread a chance to increase pool size
-            long waitTill = System.currentTimeMillis() + maxQueueWaitTime;
-               
-            do {
-                try {
-                    wait(maxQueueWaitTime);
-                }
-                catch (InterruptedException iex) {
-                    // ignoring
-                }
-
-            } while (unusedPool.size() == 0 && (maxQueueWaitTime == 0 || 
waitTill > System.currentTimeMillis()));
-
-            if (unusedPool.size() == 0) {
-                throw new ConnectionUnavailableException(
-                        "Can't obtain connection. Request timed out. Total 
used connections: "
-                                + usedPool.size());
-            }
-        }
-
-        // get first connection... lets cycle them in FIFO manner
-        return unusedPool.remove(0);
-    }
-
-    public int getLoginTimeout() throws java.sql.SQLException {
-        return poolDataSource.getLoginTimeout();
-    }
-
-    public void setLoginTimeout(int seconds) throws java.sql.SQLException {
-        poolDataSource.setLoginTimeout(seconds);
-    }
-
-    public PrintWriter getLogWriter() throws java.sql.SQLException {
-        return poolDataSource.getLogWriter();
-    }
-
-    public void setLogWriter(PrintWriter out) throws java.sql.SQLException {
-        poolDataSource.setLogWriter(out);
-    }
-
-    /**
-     * Returns closed connection to the pool.
-     */
-    public synchronized void connectionClosed(ConnectionEvent event) {
-
-        if (shuttingDown) {
-            return;
-        }
-
-        // return connection to the pool
-        PooledConnection closedConn = (PooledConnection) event.getSource();
-
-        // remove this connection from the list of connections
-        // managed by this pool...
-        int usedInd = usedPool.indexOf(closedConn);
-        if (usedInd >= 0) {
-            usedPool.remove(usedInd);
-            unusedPool.add(closedConn);
-
-            // notify threads waiting for connections
-            notifyAll();
-        }
-        // else ....
-        // other possibility is that this is a bad connection, so just ignore 
its closing
-        // event,
-        // since it was unregistered in "connectionErrorOccurred"
-    }
-
-    /**
-     * Removes connection with an error from the pool. This method is called by
-     * PoolManager connections on connection errors to notify PoolManager that 
connection
-     * is in invalid state.
-     */
-    public synchronized void connectionErrorOccurred(ConnectionEvent event) {
-
-        if (shuttingDown) {
-            return;
-        }
-
-        // later on we should analyze the error to see if this
-        // is fatal... right now just kill this PooledConnection
-
-        PooledConnection errorSrc = (PooledConnection) event.getSource();
-
-        // remove this connection from the list of connections
-        // managed by this pool...
-
-        int usedInd = usedPool.indexOf(errorSrc);
-        if (usedInd >= 0) {
-            usedPool.remove(usedInd);
-        }
-        else {
-            int unusedInd = unusedPool.indexOf(errorSrc);
-            if (unusedInd >= 0)
-                unusedPool.remove(unusedInd);
-        }
-
-        // do not close connection,
-        // let the code that catches the exception handle it
-        // ....
-    }
-
-    static class PoolMaintenanceThread extends Thread {
-
-        private boolean shouldDie;
-        private PoolManager pool;
-
-        PoolMaintenanceThread(PoolManager pool) {
-            super.setName("PoolManagerCleanup-" + pool.hashCode());
-            super.setDaemon(true);
-            this.pool = pool;
-        }
-
-        @Override
-        public void run() {
-            // periodically wakes up to check if the pool should grow or shrink
-            while (true) {
-
-                try {
-                    // don't do it too often
-                    sleep(600000);
-                }
-                catch (InterruptedException iex) {
-                    // ignore...
-                }
-
-                synchronized (pool) {
-                    // TODO: implement a smarter algorithm for pool 
management...
-                    // right now it will simply close one connection if the 
count is
-                    // above median and there are any idle connections.
-
-                    if (shouldDie) {
-                        break;
-                    }
-
-                    int unused = pool.getCurrentlyUnused();
-                    int used = pool.getCurrentlyInUse();
-                    int total = unused + used;
-                    int median = pool.minConnections
-                            + 1
-                            + (pool.maxConnections - pool.minConnections)
-                            / 2;
-
-                    if (unused > 0 && total > median) {
-                        pool.shrinkPool(1);
-                    }
-                }
-            }
-        }
-
-        /**
-         * Stops the maintenance thread.
-         */
-        void shutdown() {
-            shouldDie = true;
-            interrupt();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    public boolean isWrapperFor(Class<?> iface) throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    public <T> T unwrap(Class<T> iface) throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1 JDBC 4.1 compatibility under Java 1.5
-     */
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new UnsupportedOperationException();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
new file mode 100644
index 0000000..2ebfeab
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ *   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.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+import javax.sql.PooledConnection;
+
+/**
+ * A {@link ConnectionPoolDataSource} implementation.
+ * <p>
+ * It is implemented as a wrapper around a non-pooled data source object.
+ * Delegates all method calls except for "getPooledConnection" to the 
underlying
+ * DataSource.
+ * 
+ * @since 4.0
+ */
+public class PooledConnectionFactory implements ConnectionPoolDataSource {
+
+       private DataSource nonPooledDatasource;
+
+       public PooledConnectionFactory(DataSource nonPooledDatasource) {
+               this.nonPooledDatasource = nonPooledDatasource;
+       }
+
+       public PooledConnectionFactory(String jdbcDriver, String connectionUrl) 
throws SQLException {
+               nonPooledDatasource = new DriverDataSource(jdbcDriver, 
connectionUrl);
+       }
+
+       @Override
+       public int getLoginTimeout() throws SQLException {
+               return nonPooledDatasource.getLoginTimeout();
+       }
+
+       @Override
+       public void setLoginTimeout(int seconds) throws SQLException {
+               nonPooledDatasource.setLoginTimeout(seconds);
+       }
+
+       @Override
+       public PrintWriter getLogWriter() throws SQLException {
+               return nonPooledDatasource.getLogWriter();
+       }
+
+       @Override
+       public void setLogWriter(PrintWriter out) throws SQLException {
+               nonPooledDatasource.setLogWriter(out);
+       }
+
+       @Override
+       public PooledConnection getPooledConnection() throws SQLException {
+               return new PooledConnectionImpl(nonPooledDatasource, null, 
null);
+       }
+
+       @Override
+       public PooledConnection getPooledConnection(String user, String 
password) throws SQLException {
+               return new PooledConnectionImpl(nonPooledDatasource, user, 
password);
+       }
+
+       /**
+        * @since 3.1
+        *
+        *        JDBC 4.1 compatibility under Java 1.7
+        */
+       public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+               throw new UnsupportedOperationException();
+       }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
index f63dcb8..eb327ff 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
@@ -32,170 +32,164 @@ import javax.sql.PooledConnection;
 import javax.sql.StatementEventListener;
 
 /**
- * PooledConnectionImpl is an implementation of a pooling wrapper for the 
database
- * connection as per JDBC3 spec. Most of the modern JDBC drivers should have 
its own
- * implementation that may be used instead of this class.
- * 
+ * PooledConnectionImpl is an implementation of a pooling wrapper for the
+ * database connection as per JDBC3 spec. Most of the modern JDBC drivers 
should
+ * have its own implementation that may be used instead of this class.
  */
 public class PooledConnectionImpl implements PooledConnection {
 
-    private Connection connectionObj;
-    private List<ConnectionEventListener> connectionEventListeners;
-    private boolean hadErrors;
-    private DataSource connectionSource;
-    private String userName;
-    private String password;
-
-    protected PooledConnectionImpl() {
-        // TODO: maybe remove synchronization and use
-        // FastArrayList from commons-collections? After
-        // all the only listener is usually pool manager.
-        this.connectionEventListeners = Collections
-                .synchronizedList(new ArrayList<ConnectionEventListener>(10));
-    }
-
-    /** Creates new PooledConnection */
-    public PooledConnectionImpl(DataSource connectionSource, String userName,
-            String password) {
-
-        this();
-
-        this.connectionSource = connectionSource;
-        this.userName = userName;
-        this.password = password;
-
-    }
-
-    public void reconnect() throws SQLException {
-        if (connectionObj != null) {
-            try {
-                connectionObj.close();
-            }
-            catch (SQLException ex) {
-                // ignore exception, since connection is expected
-                // to be in a bad state
-            }
-            finally {
-                connectionObj = null;
-            }
-        }
-
-        connectionObj = (userName != null) ? connectionSource.getConnection(
-                userName,
-                password) : connectionSource.getConnection();
-    }
-
-    public void addConnectionEventListener(ConnectionEventListener listener) {
-        synchronized (connectionEventListeners) {
-            if (!connectionEventListeners.contains(listener))
-                connectionEventListeners.add(listener);
-        }
-    }
-
-    public void removeConnectionEventListener(ConnectionEventListener 
listener) {
-        synchronized (connectionEventListeners) {
-            connectionEventListeners.remove(listener);
-        }
-    }
-
-    public void close() throws SQLException {
-
-        synchronized (connectionEventListeners) {
-            // remove all listeners
-            connectionEventListeners.clear();
-        }
-
-        if (connectionObj != null) {
-            try {
-                connectionObj.close();
-            }
-            finally {
-                connectionObj = null;
-            }
-        }
-    }
-
-    public Connection getConnection() throws SQLException {
-        if (connectionObj == null) {
-            reconnect();
-        }
-
-        // set autocommit to false to return connection
-        // always in consistent state
-        if (!connectionObj.getAutoCommit()) {
-
-            try {
-                connectionObj.setAutoCommit(true);
-            }
-            catch (SQLException sqlEx) {
-                // try applying Sybase patch
-                ConnectionWrapper.sybaseAutoCommitPatch(connectionObj, sqlEx, 
true);
-            }
-        }
-
-        connectionObj.clearWarnings();
-        return new ConnectionWrapper(connectionObj, this);
-    }
-
-    protected void returnConnectionToThePool() throws SQLException {
-        // do not return to pool bad connections
-        if (hadErrors)
-            close();
-        else
-            // notify the listeners that connection is no longer used by 
application...
-            this.connectionClosedNotification();
-    }
-
-    /**
-     * This method creates and sents an event to listeners when an error 
occurs in the
-     * underlying connection. Listeners can have special logic to analyze the 
error and do
-     * things like closing this PooledConnection (if the error is fatal), 
etc...
-     */
-    public void connectionErrorNotification(SQLException exception) {
-        // hint for later to avoid returning bad connections to the pool
-        hadErrors = true;
-
-        synchronized (connectionEventListeners) {
-            if (connectionEventListeners.size() == 0)
-                return;
-
-            ConnectionEvent closedEvent = new ConnectionEvent(this, exception);
-            for (final ConnectionEventListener nextListener : 
connectionEventListeners) {
-                nextListener.connectionErrorOccurred(closedEvent);
-            }
-        }
-    }
-
-    /**
-     * Creates and sends an event to listeners when a user closes 
java.sql.Connection
-     * object belonging to this PooledConnection.
-     */
-    protected void connectionClosedNotification() {
-        synchronized (connectionEventListeners) {
-            if (connectionEventListeners.size() == 0)
-                return;
-
-            ConnectionEvent closedEvent = new ConnectionEvent(this);
-
-            for (final ConnectionEventListener nextListener : 
connectionEventListeners) {
-                nextListener.connectionClosed(closedEvent);
-            }
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    public void addStatementEventListener(StatementEventListener listener) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    public void removeStatementEventListener(StatementEventListener listener) {
-        throw new UnsupportedOperationException();
-    }
+       private Connection connectionObj;
+       private List<ConnectionEventListener> connectionEventListeners;
+       private boolean hadErrors;
+       private DataSource dataSource;
+       private String userName;
+       private String password;
+
+       public PooledConnectionImpl(DataSource dataSource, String userName, 
String password) {
+
+               // TODO: maybe remove synchronization and use
+               // FastArrayList from commons-collections? After
+               // all the only listener is usually pool manager.
+               this.connectionEventListeners = 
Collections.synchronizedList(new ArrayList<ConnectionEventListener>(10));
+
+               this.dataSource = dataSource;
+               this.userName = userName;
+               this.password = password;
+       }
+
+       public void reconnect() throws SQLException {
+               if (connectionObj != null) {
+                       try {
+                               connectionObj.close();
+                       } catch (SQLException ex) {
+                               // ignore exception, since connection is 
expected
+                               // to be in a bad state
+                       } finally {
+                               connectionObj = null;
+                       }
+               }
+
+               connectionObj = (userName != null) ? 
dataSource.getConnection(userName, password) : dataSource.getConnection();
+       }
+
+       @Override
+       public void addConnectionEventListener(ConnectionEventListener 
listener) {
+               synchronized (connectionEventListeners) {
+                       if (!connectionEventListeners.contains(listener)) {
+                               connectionEventListeners.add(listener);
+                       }
+               }
+       }
+
+       @Override
+       public void removeConnectionEventListener(ConnectionEventListener 
listener) {
+               synchronized (connectionEventListeners) {
+                       connectionEventListeners.remove(listener);
+               }
+       }
+
+       @Override
+       public void close() throws SQLException {
+
+               synchronized (connectionEventListeners) {
+                       // remove all listeners
+                       connectionEventListeners.clear();
+               }
+
+               if (connectionObj != null) {
+                       try {
+                               connectionObj.close();
+                       } finally {
+                               connectionObj = null;
+                       }
+               }
+       }
+
+       @Override
+       public Connection getConnection() throws SQLException {
+               if (connectionObj == null) {
+                       reconnect();
+               }
+
+               // set autocommit to false to return connection
+               // always in consistent state
+               if (!connectionObj.getAutoCommit()) {
+
+                       try {
+                               connectionObj.setAutoCommit(true);
+                       } catch (SQLException sqlEx) {
+                               // try applying Sybase patch
+                               
ConnectionWrapper.sybaseAutoCommitPatch(connectionObj, sqlEx, true);
+                       }
+               }
+
+               connectionObj.clearWarnings();
+               return new ConnectionWrapper(connectionObj, this);
+       }
+
+       protected void returnConnectionToThePool() throws SQLException {
+               // do not return to pool bad connections
+               if (hadErrors)
+                       close();
+               else
+                       // notify the listeners that connection is no longer 
used by
+                       // application...
+                       this.connectionClosedNotification();
+       }
+
+       /**
+        * This method creates and sents an event to listeners when an error 
occurs
+        * in the underlying connection. Listeners can have special logic to 
analyze
+        * the error and do things like closing this PooledConnection (if the 
error
+        * is fatal), etc...
+        */
+       public void connectionErrorNotification(SQLException exception) {
+               // hint for later to avoid returning bad connections to the pool
+               hadErrors = true;
+
+               synchronized (connectionEventListeners) {
+                       if (connectionEventListeners.isEmpty()) {
+                               return;
+                       }
+
+                       ConnectionEvent closedEvent = new ConnectionEvent(this, 
exception);
+                       for (ConnectionEventListener nextListener : 
connectionEventListeners) {
+                               
nextListener.connectionErrorOccurred(closedEvent);
+                       }
+               }
+       }
+
+       /**
+        * Creates and sends an event to listeners when a user closes
+        * java.sql.Connection object belonging to this PooledConnection.
+        */
+       protected void connectionClosedNotification() {
+               synchronized (connectionEventListeners) {
+                       if (connectionEventListeners.size() == 0) {
+                               return;
+                       }
+
+                       ConnectionEvent closedEvent = new ConnectionEvent(this);
+
+                       for (ConnectionEventListener nextListener : 
connectionEventListeners) {
+                               nextListener.connectionClosed(closedEvent);
+                       }
+               }
+       }
+
+       /**
+        * @since 3.0
+        */
+       @Override
+       public void addStatementEventListener(StatementEventListener listener) {
+               throw new UnsupportedOperationException("Statement events are 
unsupported");
+       }
+
+       /**
+        * @since 3.0
+        */
+       @Override
+       public void removeStatementEventListener(StatementEventListener 
listener) {
+               throw new UnsupportedOperationException("Statement events are 
unsupported");
+       }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
new file mode 100644
index 0000000..cccf905
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
@@ -0,0 +1,471 @@
+/*****************************************************************
+ *   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.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.logging.Logger;
+
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+import javax.sql.PooledConnection;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.di.ScopeEventListener;
+
+/**
+ * A {@link DataSource} with a pool of connections, that can automatically grow
+ * to the max size as more connections are requested.
+ * 
+ * @since 4.0
+ */
+public class PoolingDataSource implements ScopeEventListener, DataSource, 
ConnectionEventListener {
+
+       /**
+        * Defines a maximum time in milliseconds that a connection request 
could
+        * wait in the connection queue. After this period expires, an exception
+        * will be thrown in the calling method.
+        */
+       public static final int MAX_QUEUE_WAIT_DEFAULT = 20000;
+
+       /**
+        * An exception indicating that a connection request waiting in the 
queue
+        * timed out and was unable to obtain a connection.
+        */
+       public static class ConnectionUnavailableException extends SQLException 
{
+               private static final long serialVersionUID = 
1063973806941023165L;
+
+               public ConnectionUnavailableException(String message) {
+                       super(message);
+               }
+       }
+
+       protected ConnectionPoolDataSource pooledConnectionFactory;
+
+       private int minConnections;
+       private int maxConnections;
+       private long maxQueueWaitTime;
+       private String validationQuery;
+
+       private List<PooledConnection> unusedPool;
+       private List<PooledConnection> usedPool;
+       private boolean shuttingDown;
+
+       /**
+        * Creates new PoolManager with the specified policy for connection 
pooling
+        * and a ConnectionPoolDataSource object.
+        * 
+        * @param poolDataSource
+        *            data source for pooled connections
+        * @param minCons
+        *            Non-negative integer that specifies a minimum number of 
open
+        *            connections to keep in the pool at all times
+        * @param maxCons
+        *            Non-negative integer that specifies maximum number of
+        *            simultaneously open connections
+        * @throws SQLException
+        *             if pool manager can not be created.
+        * @since 4.0
+        */
+       public PoolingDataSource(ConnectionPoolDataSource poolDataSource, 
PoolingDataSourceParameters parameters)
+                       throws SQLException {
+
+               this.pooledConnectionFactory = poolDataSource;
+
+               // clone parameters to keep DataSource immutable
+               this.minConnections = parameters.getMinConnections();
+               this.maxConnections = parameters.getMaxConnections();
+               this.maxQueueWaitTime = parameters.getMaxQueueWaitTime();
+               this.validationQuery = parameters.getValidationQuery();
+
+               // init pool... use linked lists to use the queue in the FIFO 
manner
+               this.usedPool = new LinkedList<PooledConnection>();
+               this.unusedPool = new LinkedList<PooledConnection>();
+               growPool(minConnections);
+       }
+
+       /**
+        * Creates and returns new PooledConnection object, adding itself as a
+        * listener for connection events.
+        */
+       protected PooledConnection newPooledConnection() throws SQLException {
+               PooledConnection connection = 
pooledConnectionFactory.getPooledConnection();
+               connection.addConnectionEventListener(this);
+               return connection;
+       }
+
+       /**
+        * Shuts down the pool, closing all open connections. This is an
+        * implementation of {@link ScopeEventListener}.
+        * 
+        * @since 3.1
+        */
+       @Override
+       public synchronized void beforeScopeEnd() {
+               try {
+
+                       // using boolean variable instead of locking 
PoolManager instance
+                       // due to possible deadlock during shutdown when one of 
connections
+                       // locks its event listeners list trying to invoke 
locked
+                       // PoolManager's listener methods
+                       shuttingDown = true;
+
+                       ListIterator<PooledConnection> unusedIterator = 
unusedPool.listIterator();
+                       while (unusedIterator.hasNext()) {
+                               PooledConnection con = unusedIterator.next();
+                               // close connection
+                               con.close();
+                               // remove connection from the list
+                               unusedIterator.remove();
+                       }
+
+                       // clean used connections
+                       ListIterator<PooledConnection> usedIterator = 
usedPool.listIterator();
+                       while (usedIterator.hasNext()) {
+                               PooledConnection con = usedIterator.next();
+                               // stop listening for connection events
+                               con.removeConnectionEventListener(this);
+                               // close connection
+                               con.close();
+                               // remove connection from the list
+                               usedIterator.remove();
+                       }
+               } catch (SQLException e) {
+                       throw new CayenneRuntimeException("Error while shutting 
down");
+               }
+       }
+
+       /**
+        * @return true if at least one more connection can be added to the 
pool.
+        */
+       protected synchronized boolean canGrowPool() {
+               return getPoolSize() < maxConnections;
+       }
+
+       /**
+        * Increases connection pool by the specified number of connections.
+        * 
+        * @return the actual number of created connections.
+        * @throws SQLException
+        *             if an error happens when creating a new connection.
+        */
+       protected synchronized int growPool(int addConnections) throws 
SQLException {
+
+               int i = 0;
+               int startPoolSize = getPoolSize();
+               for (; i < addConnections && startPoolSize + i < 
maxConnections; i++) {
+                       PooledConnection newConnection = newPooledConnection();
+                       unusedPool.add(newConnection);
+               }
+
+               return i;
+       }
+
+       public synchronized void shrinkPool(int closeConnections) {
+               int idleSize = unusedPool.size();
+               for (int i = 0; i < closeConnections && i < idleSize; i++) {
+                       PooledConnection con = unusedPool.remove(i);
+
+                       try {
+                               con.close();
+                       } catch (SQLException ex) {
+                               // ignore
+                       }
+               }
+       }
+
+       public String getValidationQuery() {
+               return validationQuery;
+       }
+
+       /**
+        * Returns maximum number of connections this pool can keep. This 
parameter
+        * when configured allows to limit the number of simultaneously open
+        * connections.
+        */
+       public int getMaxConnections() {
+               return maxConnections;
+       }
+
+       /**
+        * Returns the absolute minimum number of connections allowed in this 
pool
+        * at any moment in time.
+        */
+       public int getMinConnections() {
+               return minConnections;
+       }
+
+       /**
+        * Returns current number of connections.
+        */
+       public synchronized int getPoolSize() {
+               return usedPool.size() + unusedPool.size();
+       }
+
+       /**
+        * Returns the number of connections obtained via this DataSource that 
are
+        * currently in use by the DataSource clients.
+        */
+       public synchronized int getCurrentlyInUse() {
+               return usedPool.size();
+       }
+
+       /**
+        * Returns the number of connections maintained in the pool that are
+        * currently not used by any clients and are available immediately via
+        * <code>getConnection</code> method.
+        */
+       public synchronized int getCurrentlyUnused() {
+               return unusedPool.size();
+       }
+
+       /**
+        * Returns connection from the pool using internal values of user name 
and
+        * password.
+        */
+       @Override
+       public synchronized Connection getConnection() throws SQLException {
+               if (shuttingDown) {
+                       throw new SQLException("Pool manager is shutting 
down.");
+               }
+
+               PooledConnection pooledConnection = uncheckPooledConnection();
+
+               try {
+                       return uncheckAndValidateConnection(pooledConnection);
+               } catch (SQLException ex) {
+
+                       try {
+                               pooledConnection.close();
+                       } catch (SQLException ignored) {
+                       }
+
+                       // do one reconnect attempt...
+                       pooledConnection = uncheckPooledConnection();
+                       try {
+                               return 
uncheckAndValidateConnection(pooledConnection);
+                       } catch (SQLException reconnectEx) {
+                               try {
+                                       pooledConnection.close();
+                               } catch (SQLException ignored) {
+                               }
+
+                               throw reconnectEx;
+                       }
+               }
+       }
+
+       /**
+        * Returns connection from the pool.
+        */
+       @Override
+       public synchronized Connection getConnection(String userName, String 
password) throws SQLException {
+               throw new UnsupportedOperationException(
+                               "Connections for a specific user are not 
supported by the pooled DataSource");
+       }
+
+       private Connection uncheckConnection(PooledConnection pooledConnection) 
throws SQLException {
+               Connection c = pooledConnection.getConnection();
+
+               // only do that on successfully unchecked connection...
+               usedPool.add(pooledConnection);
+               return c;
+       }
+
+       private Connection uncheckAndValidateConnection(PooledConnection 
pooledConnection) throws SQLException {
+               Connection c = uncheckConnection(pooledConnection);
+
+               if (validationQuery != null) {
+
+                       Statement statement = c.createStatement();
+                       try {
+                               ResultSet rs = 
statement.executeQuery(validationQuery);
+                               try {
+
+                                       if (!rs.next()) {
+                                               throw new 
SQLException("Connection validation failed, no result for query: " + 
validationQuery);
+                                       }
+
+                               } finally {
+                                       rs.close();
+                               }
+                       } finally {
+                               statement.close();
+                       }
+               }
+
+               return c;
+       }
+
+       private PooledConnection uncheckPooledConnection() throws SQLException {
+               // wait for returned connections or the maintenance thread
+               // to bump the pool size...
+
+               if (unusedPool.size() == 0) {
+
+                       // first try to open a new connection
+                       if (canGrowPool()) {
+                               return newPooledConnection();
+                       }
+
+                       // can't open no more... will have to wait for others 
to return a
+                       // connection
+
+                       // note that if we were woken up
+                       // before the full wait period expired, and no 
connections are
+                       // available yet, go back to sleep. Otherwise we don't 
give a
+                       // maintenance
+                       // thread a chance to increase pool size
+                       long waitTill = System.currentTimeMillis() + 
maxQueueWaitTime;
+
+                       do {
+                               try {
+                                       wait(maxQueueWaitTime);
+                               } catch (InterruptedException iex) {
+                                       // ignoring
+                               }
+
+                       } while (unusedPool.size() == 0 && (maxQueueWaitTime == 
0 || waitTill > System.currentTimeMillis()));
+
+                       if (unusedPool.size() == 0) {
+                               throw new ConnectionUnavailableException(
+                                               "Can't obtain connection. 
Request timed out. Total used connections: " + usedPool.size());
+                       }
+               }
+
+               // get first connection... lets cycle them in FIFO manner
+               return unusedPool.remove(0);
+       }
+
+       @Override
+       public int getLoginTimeout() throws java.sql.SQLException {
+               return pooledConnectionFactory.getLoginTimeout();
+       }
+
+       @Override
+       public void setLoginTimeout(int seconds) throws java.sql.SQLException {
+               pooledConnectionFactory.setLoginTimeout(seconds);
+       }
+
+       @Override
+       public PrintWriter getLogWriter() throws java.sql.SQLException {
+               return pooledConnectionFactory.getLogWriter();
+       }
+
+       @Override
+       public void setLogWriter(PrintWriter out) throws java.sql.SQLException {
+               pooledConnectionFactory.setLogWriter(out);
+       }
+
+       /**
+        * Returns closed connection to the pool.
+        */
+       @Override
+       public synchronized void connectionClosed(ConnectionEvent event) {
+
+               if (shuttingDown) {
+                       return;
+               }
+
+               // return connection to the pool
+               PooledConnection closedConn = (PooledConnection) 
event.getSource();
+
+               // remove this connection from the list of connections
+               // managed by this pool...
+               int usedInd = usedPool.indexOf(closedConn);
+               if (usedInd >= 0) {
+                       usedPool.remove(usedInd);
+                       unusedPool.add(closedConn);
+
+                       // notify threads waiting for connections
+                       notifyAll();
+               }
+               // else ....
+               // other possibility is that this is a bad connection, so just 
ignore
+               // its closing
+               // event,
+               // since it was unregistered in "connectionErrorOccurred"
+       }
+
+       /**
+        * Removes connection with an error from the pool. This method is 
called by
+        * PoolManager connections on connection errors to notify PoolManager 
that
+        * connection is in invalid state.
+        */
+       @Override
+       public synchronized void connectionErrorOccurred(ConnectionEvent event) 
{
+
+               if (shuttingDown) {
+                       return;
+               }
+
+               // later on we should analyze the error to see if this
+               // is fatal... right now just kill this PooledConnection
+
+               PooledConnection errorSrc = (PooledConnection) 
event.getSource();
+
+               // remove this connection from the list of connections
+               // managed by this pool...
+
+               int usedInd = usedPool.indexOf(errorSrc);
+               if (usedInd >= 0) {
+                       usedPool.remove(usedInd);
+               } else {
+                       int unusedInd = unusedPool.indexOf(errorSrc);
+                       if (unusedInd >= 0) {
+                               unusedPool.remove(unusedInd);
+                       }
+               }
+
+               // do not close connection,
+               // let the code that catches the exception handle it
+               // ....
+       }
+
+       @Override
+       public boolean isWrapperFor(Class<?> iface) throws SQLException {
+               return PoolingDataSource.class.equals(iface);
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> T unwrap(Class<T> iface) throws SQLException {
+               if (PoolingDataSource.class.equals(iface)) {
+                       return (T) this;
+               }
+
+               throw new SQLException("Not a wrapper for " + iface);
+       }
+
+       // JDBC 4.1 compatibility under Java <= 1.6
+       public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+               throw new UnsupportedOperationException();
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
new file mode 100644
index 0000000..a03fd39
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
@@ -0,0 +1,64 @@
+/*****************************************************************
+ *   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.cayenne.conn;
+
+/**
+ * A connection of pooling parameters used by {@link PoolingDataSource}.
+ * 
+ * @since 4.0
+ */
+public class PoolingDataSourceParameters {
+
+       private String validationQuery;
+       private int minConnections;
+       private int maxConnections;
+       private long maxQueueWaitTime;
+
+       public int getMinConnections() {
+               return minConnections;
+       }
+
+       public void setMinConnections(int minConnections) {
+               this.minConnections = minConnections;
+       }
+
+       public int getMaxConnections() {
+               return maxConnections;
+       }
+
+       public void setMaxConnections(int maxConnections) {
+               this.maxConnections = maxConnections;
+       }
+
+       public long getMaxQueueWaitTime() {
+               return maxQueueWaitTime;
+       }
+
+       public void setMaxQueueWaitTime(long maxQueueWaitTime) {
+               this.maxQueueWaitTime = maxQueueWaitTime;
+       }
+
+       public String getValidationQuery() {
+               return validationQuery;
+       }
+
+       public void setValidationQuery(String validationQuery) {
+               this.validationQuery = validationQuery;
+       }
+}

Reply via email to