You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2015/05/03 11:18:33 UTC

[1/3] cayenne git commit: CAY-2009 Non-blocking connection pool

Repository: cayenne
Updated Branches:
  refs/heads/master d28c03635 -> 517df07b7


http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_FailingValidationQueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_FailingValidationQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_FailingValidationQueryIT.java
new file mode 100644
index 0000000..7f7d854
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_FailingValidationQueryIT.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.sql.Driver;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.conn.DataSourceInfo;
+import org.apache.cayenne.datasource.PoolingDataSource;
+import org.apache.cayenne.datasource.PoolingDataSourceParameters;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class PoolingDataSource_FailingValidationQueryIT extends ServerCase {
+
+	@Inject
+	private DataSourceInfo dataSourceInfo;
+
+	@Inject
+	private AdhocObjectFactory objectFactory;
+
+	@Inject
+	private JdbcEventLogger logger;
+
+	protected PoolingDataSourceParameters createParameters() {
+		PoolingDataSourceParameters poolParameters = new PoolingDataSourceParameters();
+		poolParameters.setMinConnections(2);
+		poolParameters.setMaxConnections(3);
+		poolParameters.setMaxQueueWaitTime(1000);
+		poolParameters.setValidationQuery("SELECT count(1) FROM NO_SUCH_TABLE");
+		return poolParameters;
+	}
+
+	@Test(expected = CayenneRuntimeException.class)
+	public void testConstructor() throws Exception {
+		Driver driver = objectFactory.newInstance(Driver.class, dataSourceInfo.getJdbcDriver());
+		DriverDataSource nonPooling = new DriverDataSource(driver, dataSourceInfo.getDataSourceUrl(),
+				dataSourceInfo.getUserName(), dataSourceInfo.getPassword());
+		nonPooling.setLogger(logger);
+
+		PoolingDataSourceParameters poolParameters = createParameters();
+		new PoolingDataSource(nonPooling, poolParameters);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_StaticsTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_StaticsTest.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_StaticsTest.java
new file mode 100644
index 0000000..114f621
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_StaticsTest.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   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.datasource;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.cayenne.datasource.PoolingDataSource;
+import org.junit.Test;
+
+public class PoolingDataSource_StaticsTest {
+
+	@Test
+	public void testMaxIdleConnections() {
+
+		assertEquals(1, PoolingDataSource.maxIdleConnections(1, 1));
+		assertEquals(2, PoolingDataSource.maxIdleConnections(1, 2));
+		assertEquals(1, PoolingDataSource.maxIdleConnections(0, 2));
+		assertEquals(2, PoolingDataSource.maxIdleConnections(0, 3));
+		assertEquals(2, PoolingDataSource.maxIdleConnections(0, 4));
+		assertEquals(3, PoolingDataSource.maxIdleConnections(0, 5));
+		assertEquals(6, PoolingDataSource.maxIdleConnections(5, 6));
+
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_ValidationQueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_ValidationQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_ValidationQueryIT.java
new file mode 100644
index 0000000..5d2a886
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSource_ValidationQueryIT.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   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.datasource;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+
+import org.apache.cayenne.datasource.PoolingDataSourceParameters;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class PoolingDataSource_ValidationQueryIT extends BasePoolingDataSourceIT {
+
+	@Override
+	protected PoolingDataSourceParameters createParameters() {
+		PoolingDataSourceParameters params = super.createParameters();
+		params.setValidationQuery("SELECT count(1) FROM ARTIST");
+		return params;
+	}
+
+	@Test
+	public void testGetConnection_ValidationQuery() throws Exception {
+
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(2, dataSource.availableSize());
+
+		Connection c1 = dataSource.getConnection();
+
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(1, dataSource.availableSize());
+
+		c1.close();
+
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(2, dataSource.availableSize());
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataSourceFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataSourceFactory.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataSourceFactory.java
index 43ef5f3..be4df2f 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataSourceFactory.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataSourceFactory.java
@@ -26,8 +26,8 @@ import java.util.Set;
 
 import javax.sql.DataSource;
 
-import org.apache.cayenne.conn.DataSourceBuilder;
 import org.apache.cayenne.conn.DataSourceInfo;
+import org.apache.cayenne.datasource.DataSourceBuilder;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
index f0f4ff2..634213b 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
@@ -23,7 +23,7 @@ import java.sql.Driver;
 import java.util.Collections;
 
 import org.apache.cayenne.access.DbGenerator;
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-tools/src/main/java/org/apache/cayenne/tools/configuration/DriverDataSourceFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/configuration/DriverDataSourceFactory.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/configuration/DriverDataSourceFactory.java
index c45eefd..56c1a05 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/configuration/DriverDataSourceFactory.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/configuration/DriverDataSourceFactory.java
@@ -25,7 +25,7 @@ import javax.sql.DataSource;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.server.DataSourceFactory;
 import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 5efb96d..ecc2b48 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -21,6 +21,7 @@ CAY-2001 Saving a display state of Project
 CAY-2004 EJBQL: Support for ordering on aggregate expressions
 CAY-2007 Refactoring SelectTranslator for better extensibility
 CAY-2008 Connection pool refactoring and validation query support in Cayenne DataSource
+CAY-2009 Non-blocking connection pool
 
 Bug Fixes:
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/DataSourcePreferences.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/DataSourcePreferences.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/DataSourcePreferences.java
index 94ea608..1092624 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/DataSourcePreferences.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/DataSourcePreferences.java
@@ -36,7 +36,7 @@ import java.util.prefs.Preferences;
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JOptionPane;
 
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.modeler.FileClassLoadingService;
 import org.apache.cayenne.modeler.pref.DBConnectionInfo;
 import org.apache.cayenne.modeler.util.CayenneController;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/DBConnectionInfo.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/DBConnectionInfo.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/DBConnectionInfo.java
index 005c585..e91fabf 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/DBConnectionInfo.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/DBConnectionInfo.java
@@ -27,7 +27,7 @@ import javax.sql.DataSource;
 
 import org.apache.cayenne.configuration.server.DbAdapterFactory;
 import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.AutoAdapter;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
----------------------------------------------------------------------
diff --git a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
index ad4c6e9..e15c65e 100644
--- a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
+++ b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
@@ -23,7 +23,7 @@ import java.io.File;
 import java.sql.Driver;
 
 import org.apache.cayenne.access.DbGenerator;
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;


[3/3] cayenne git commit: CAY-2009 Non-blocking connection pool

Posted by aa...@apache.org.
CAY-2009 Non-blocking connection pool

    * moving everything into a new datasource package ("conn" package name makes no sense)
    * stopped using javax.sql.ConnectionPoolDataSource. Using simple delegation
      to a non-pooled DataSource instead
    * non-blocking data structures for operating the pool


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

Branch: refs/heads/master
Commit: 517df07b7665fb7786b4ffd519e8c9a2f3855ff9
Parents: d28c036
Author: aadamchik <aa...@apache.org>
Authored: Sun May 3 05:14:19 2015 -0400
Committer: aadamchik <aa...@apache.org>
Committed: Sun May 3 05:15:06 2015 -0400

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DbGenerator.java  |   2 +-
 .../server/PropertyDataSourceFactory.java       |   4 +-
 .../server/XMLPoolingDataSourceFactory.java     |   4 +-
 .../apache/cayenne/conn/ConnectionWrapper.java  | 749 -------------------
 .../apache/cayenne/conn/DataSourceBuilder.java  | 140 ----
 .../apache/cayenne/conn/DriverDataSource.java   | 298 --------
 .../cayenne/conn/ManagedPoolingDataSource.java  | 155 ----
 .../cayenne/conn/PooledConnectionFactory.java   |  90 ---
 .../cayenne/conn/PooledConnectionImpl.java      | 195 -----
 .../apache/cayenne/conn/PoolingDataSource.java  | 471 ------------
 .../conn/PoolingDataSourceParameters.java       |  64 --
 .../java/org/apache/cayenne/conn/package.html   |  26 -
 .../datasource/BadValidationQueryException.java |  41 +
 .../cayenne/datasource/DataSourceBuilder.java   | 135 ++++
 .../cayenne/datasource/DriverDataSource.java    | 298 ++++++++
 .../datasource/ManagedPoolingDataSource.java    | 107 +++
 .../cayenne/datasource/PoolAwareConnection.java | 609 +++++++++++++++
 .../cayenne/datasource/PoolingDataSource.java   | 314 ++++++++
 .../datasource/PoolingDataSourceManager.java    |  65 ++
 .../datasource/PoolingDataSourceParameters.java |  64 ++
 .../cayenne/datasource/StoppedDataSource.java   |  88 +++
 .../cayenne/conn/BasePoolingDataSourceIT.java   |  74 --
 .../cayenne/conn/DriverDataSourceTest.java      |  43 --
 .../cayenne/conn/PooledConnectionImplTest.java  |  67 --
 .../cayenne/conn/PoolingDataSourceIT.java       |  72 --
 ...lingDataSource_FailingValidationQueryIT.java |  43 --
 .../PoolingDataSource_ValidationQueryIT.java    |  55 --
 .../datasource/BasePoolingDataSourceIT.java     |  77 ++
 .../datasource/DriverDataSourceTest.java        |  44 ++
 .../datasource/PoolAwareConnectionTest.java     |  99 +++
 .../cayenne/datasource/PoolingDataSourceIT.java | 160 ++++
 .../datasource/PoolingDataSourceTest.java       | 196 +++++
 ...lingDataSource_FailingValidationQueryIT.java |  66 ++
 .../PoolingDataSource_StaticsTest.java          |  41 +
 .../PoolingDataSource_ValidationQueryIT.java    |  56 ++
 .../di/server/ServerCaseDataSourceFactory.java  |   2 +-
 .../apache/cayenne/tools/DbGeneratorTask.java   |   2 +-
 .../configuration/DriverDataSourceFactory.java  |   2 +-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 .../dialog/pref/DataSourcePreferences.java      |   2 +-
 .../cayenne/modeler/pref/DBConnectionInfo.java  |   2 +-
 .../apache/cayenne/tools/DbGeneratorMojo.java   |   2 +-
 42 files changed, 2472 insertions(+), 2553 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/access/DbGenerator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DbGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DbGenerator.java
index f31f61c..275e07e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbGenerator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DbGenerator.java
@@ -36,7 +36,7 @@ import javax.sql.DataSource;
 
 import org.apache.cayenne.ashwood.AshwoodEntitySorter;
 import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.DriverDataSource;
+import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.TypesMapping;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/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 60bf717..0826d7c 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
@@ -24,8 +24,8 @@ 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.PoolingDataSource;
+import org.apache.cayenne.datasource.DataSourceBuilder;
+import org.apache.cayenne.datasource.PoolingDataSource;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/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 a62a2d9..c333325 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
@@ -24,9 +24,9 @@ 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.PoolingDataSource;
+import org.apache.cayenne.datasource.DataSourceBuilder;
+import org.apache.cayenne.datasource.PoolingDataSource;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/conn/ConnectionWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/ConnectionWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/ConnectionWrapper.java
deleted file mode 100644
index 006a72a..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/ConnectionWrapper.java
+++ /dev/null
@@ -1,749 +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.sql.Array;
-import java.sql.Blob;
-import java.sql.CallableStatement;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.NClob;
-import java.sql.PreparedStatement;
-import java.sql.SQLClientInfoException;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.SQLXML;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.sql.Struct;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.Executor;
-
-/**
- * ConnectionWrapper is a <code>java.sql.Connection</code> implementation that wraps
- * another Connection, delegating method calls to this connection. It works in conjunction
- * with PooledConnectionImpl, to generate pool events, provide limited automated
- * reconnection functionality, etc.
- * 
- */
-public class ConnectionWrapper implements Connection {
-
-    private Connection connection;
-    private PooledConnectionImpl pooledConnection;
-    private long lastReconnected;
-    private int reconnectCount;
-
-    /**
-     * Fixes Sybase problems with autocommit. Used idea from Jonas
-     * org.objectweb.jonas.jdbc_xa.ConnectionImpl (http://www.objectweb.org/jonas/).
-     * <p>
-     * If problem is not the one that can be fixed by this patch, original exception is
-     * rethrown. If exception occurs when fixing the problem, new exception is thrown.
-     * </p>
-     */
-    static void sybaseAutoCommitPatch(Connection c, SQLException e, boolean autoCommit)
-            throws SQLException {
-
-        String s = e.getMessage().toLowerCase();
-        if (s.contains("set chained command not allowed")) {
-            c.commit();
-            c.setAutoCommit(autoCommit); // Shouldn't fail now.
-        }
-        else {
-            throw e;
-        }
-    }
-
-    /**
-     * Creates new ConnectionWrapper
-     */
-    public ConnectionWrapper(Connection connection, PooledConnectionImpl pooledConnection) {
-        this.connection = connection;
-        this.pooledConnection = pooledConnection;
-    }
-
-    protected void reconnect(SQLException exception) throws SQLException {
-
-        // if there was a relatively recent reconnect, just rethrow an error
-        // and retire itself. THIS WILL PREVENT RECONNECT LOOPS
-        if (reconnectCount > 0 && System.currentTimeMillis() - lastReconnected < 60000) {
-
-            retire(exception);
-            throw exception;
-        }
-
-        pooledConnection.reconnect();
-
-        // Pooled connection will wrap returned connection into
-        // another ConnectionWrapper.... lets get the real connection
-        // underneath...
-        Connection connection = pooledConnection.getConnection();
-        if (connection instanceof ConnectionWrapper) {
-            this.connection = ((ConnectionWrapper) connection).connection;
-        }
-        else {
-            this.connection = connection;
-        }
-
-        lastReconnected = System.currentTimeMillis();
-        reconnectCount++;
-    }
-
-    protected void retire(SQLException exception) {
-        // notify all the listeners....
-        pooledConnection.connectionErrorNotification(exception);
-    }
-
-    @Override
-    public void clearWarnings() throws SQLException {
-        try {
-            connection.clearWarnings();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void close() throws SQLException {
-        if (null != pooledConnection) {
-            pooledConnection.returnConnectionToThePool();
-        }
-        connection = null;
-        pooledConnection = null;
-    }
-
-    @Override
-    public void commit() throws SQLException {
-        try {
-            connection.commit();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public Statement createStatement() throws SQLException {
-        try {
-            return connection.createStatement();
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createStatement();
-        }
-    }
-
-    @Override
-    public Statement createStatement(int resultSetType, int resultSetConcurrency)
-            throws SQLException {
-        try {
-            return connection.createStatement(resultSetType, resultSetConcurrency);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createStatement(resultSetType, resultSetConcurrency);
-        }
-    }
-
-    @Override
-    public boolean getAutoCommit() throws SQLException {
-        try {
-            return connection.getAutoCommit();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public String getCatalog() throws SQLException {
-        try {
-            return connection.getCatalog();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public DatabaseMetaData getMetaData() throws SQLException {
-        try {
-            return connection.getMetaData();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public int getTransactionIsolation() throws SQLException {
-        try {
-            return connection.getTransactionIsolation();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public SQLWarning getWarnings() throws SQLException {
-        try {
-            return connection.getWarnings();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public boolean isClosed() throws SQLException {
-        if (connection != null) {
-            try {
-                return connection.isClosed();
-            }
-            catch (SQLException sqlEx) {
-                retire(sqlEx);
-                throw sqlEx;
-            }
-        }
-        else
-            return true;
-    }
-
-    @Override
-    public boolean isReadOnly() throws SQLException {
-        try {
-            return connection.isReadOnly();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public String nativeSQL(String sql) throws SQLException {
-        try {
-            return connection.nativeSQL(sql);
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public CallableStatement prepareCall(String sql) throws SQLException {
-        try {
-            return connection.prepareCall(sql);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareCall(sql);
-        }
-    }
-
-    @Override
-    public CallableStatement prepareCall(
-            String sql,
-            int resultSetType,
-            int resultSetConcurrency) throws SQLException {
-        try {
-            return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareCall(sql, resultSetType, resultSetConcurrency);
-        }
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql) throws SQLException {
-        try {
-            return connection.prepareStatement(sql);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareStatement(sql);
-        }
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(
-            String sql,
-            int resultSetType,
-            int resultSetConcurrency) throws SQLException {
-        try {
-            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareStatement(sql, resultSetType, resultSetConcurrency);
-        }
-    }
-
-    @Override
-    public void rollback() throws SQLException {
-        try {
-            connection.rollback();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void setAutoCommit(boolean autoCommit) throws SQLException {
-        try {
-            connection.setAutoCommit(autoCommit);
-        }
-        catch (SQLException sqlEx) {
-
-            try {
-                // apply Sybase patch
-                sybaseAutoCommitPatch(connection, sqlEx, autoCommit);
-            }
-            catch (SQLException patchEx) {
-                retire(sqlEx);
-                throw sqlEx;
-            }
-        }
-    }
-
-    @Override
-    public void setCatalog(String catalog) throws SQLException {
-        try {
-            connection.setCatalog(catalog);
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void setReadOnly(boolean readOnly) throws SQLException {
-        try {
-            connection.setReadOnly(readOnly);
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void setTransactionIsolation(int level) throws SQLException {
-        try {
-            connection.setTransactionIsolation(level);
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public Map<String,Class<?>> getTypeMap() throws SQLException {
-        try {
-            return connection.getTypeMap();
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
-        try {
-            connection.setTypeMap(map);
-        }
-        catch (SQLException sqlEx) {
-            retire(sqlEx);
-            throw sqlEx;
-        }
-    }
-
-    @Override
-    public void setHoldability(int holdability) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method setHoldability() not yet implemented.");
-    }
-
-    @Override
-    public int getHoldability() throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method getHoldability() not yet implemented.");
-    }
-
-    @Override
-    public Savepoint setSavepoint() throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method setSavepoint() not yet implemented.");
-    }
-
-    @Override
-    public Savepoint setSavepoint(String name) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method setSavepoint() not yet implemented.");
-    }
-
-    @Override
-    public void rollback(Savepoint savepoint) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method rollback() not yet implemented.");
-    }
-
-    @Override
-    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method releaseSavepoint() not yet implemented.");
-    }
-
-    @Override
-    public Statement createStatement(
-            int resultSetType,
-            int resultSetConcurrency,
-            int resultSetHoldability) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method createStatement() not yet implemented.");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(
-            String sql,
-            int resultSetType,
-            int resultSetConcurrency,
-            int resultSetHoldability) throws SQLException {
-        throw new java.lang.UnsupportedOperationException(
-                "Method prepareStatement() not yet implemented.");
-    }
-
-    @Override
-    public CallableStatement prepareCall(
-            String sql,
-            int resultSetType,
-            int resultSetConcurrency,
-            int resultSetHoldability) throws SQLException {
-        try {
-            return connection.prepareCall(
-                    sql,
-                    resultSetType,
-                    resultSetConcurrency,
-                    resultSetHoldability);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareCall(
-                    sql,
-                    resultSetType,
-                    resultSetConcurrency,
-                    resultSetHoldability);
-        }
-    }
-    
-    @Override
-    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
-            throws SQLException {
-
-        try {
-            return connection.prepareStatement(sql, autoGeneratedKeys);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareStatement(sql, autoGeneratedKeys);
-        }
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
-            throws SQLException {
-        try {
-            return connection.prepareStatement(sql, columnIndexes);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareStatement(sql, columnIndexes);
-        }
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, String[] columnNames)
-            throws SQLException {
-        try {
-            return connection.prepareStatement(sql, columnNames);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return prepareStatement(sql, columnNames);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
-        try {
-            return connection.createArrayOf(typeName, elements);
-        }
-        catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createArrayOf(typeName, elements);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Blob createBlob() throws SQLException {
-        try {
-            return connection.createBlob();
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createBlob();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Clob createClob() throws SQLException {
-        try {
-            return connection.createClob();
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createClob();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
-        try {
-            return connection.createStruct(typeName, attributes);
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createStruct(typeName, attributes);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Properties getClientInfo() throws SQLException {
-        try {
-            return connection.getClientInfo();
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return getClientInfo();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public String getClientInfo(String name) throws SQLException {
-        try {
-            return connection.getClientInfo(name);
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return getClientInfo(name);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public boolean isValid(int timeout) throws SQLException {
-        try {
-            return connection.isValid(timeout);
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return isValid(timeout);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public boolean isWrapperFor(Class<?> iface) throws SQLException {
-        // TODO: we can implement that now.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public <T> T unwrap(Class<T> iface) throws SQLException {
-        // TODO: we can implement that now.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public NClob createNClob() throws SQLException {
-        try {
-            return connection.createNClob();
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createNClob();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public SQLXML createSQLXML() throws SQLException {
-        try {
-            return connection.createSQLXML();
-        } catch (SQLException sqlEx) {
-
-            // reconnect has code to prevent loops
-            reconnect(sqlEx);
-            return createSQLXML();
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    @Override
-    public void setClientInfo(Properties properties) throws SQLClientInfoException {
-        // TODO: we can implement that now.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.0
-     */
-    // JDBC 4 compatibility under Java 1.5
-    @Override
-    public void setClientInfo(String name, String value) throws SQLClientInfoException {
-        // TODO: we can implement that now.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1
-     *
-     * JDBC 4.1 compatibility under Java 1.5
-     */
-    public void setSchema(String schema) throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1
-     *
-     * JDBC 4.1 compatibility under Java 1.5
-     */
-    public String getSchema() throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1
-     *
-     * JDBC 4.1 compatibility under Java 1.5
-     */
-    public void abort(Executor executor) throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1
-     *
-     * JDBC 4.1 compatibility under Java 1.5
-     */
-    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @since 3.1
-     *
-     * JDBC 4.1 compatibility under Java 1.5
-     */
-    public int getNetworkTimeout() throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/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
deleted file mode 100644
index 504a053..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
+++ /dev/null
@@ -1,140 +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.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 DataSourceBuilder validationQuery(String validationQuery) {
-		poolParameters.setValidationQuery(validationQuery);
-		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/517df07b/cayenne-server/src/main/java/org/apache/cayenne/conn/DriverDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/DriverDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/DriverDataSource.java
deleted file mode 100644
index 0cf9afc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/DriverDataSource.java
+++ /dev/null
@@ -1,298 +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.Driver;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import javax.sql.DataSource;
-
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.util.Util;
-
-/**
- * A non-pooling DataSource implementation wrapping a JDBC driver.
- * 
- */
-public class DriverDataSource implements DataSource {
-
-    protected Driver _driver;
-    protected String driverClassName;
-    
-    protected String connectionUrl;
-    protected String userName;
-    protected String password;
-
-    protected JdbcEventLogger logger;
-
-    /**
-     * Loads JDBC driver using current thread class loader.
-     * 
-     * @since 3.0
-     * @deprecated since 4.0 as class loading should not happen here.
-     */
-    @Deprecated
-    private static Driver loadDriver(String driverClassName) throws SQLException {
-
-        Class<?> driverClass;
-        try {
-            driverClass = Util.getJavaClass(driverClassName);
-        }
-        catch (Exception ex) {
-            throw new SQLException("Can not load JDBC driver named '"
-                    + driverClassName
-                    + "': "
-                    + ex.getMessage());
-        }
-
-        try {
-            return (Driver) driverClass.newInstance();
-        }
-        catch (Exception ex) {
-            throw new SQLException("Error instantiating driver '"
-                    + driverClassName
-                    + "': "
-                    + ex.getMessage());
-        }
-    }
-
-    /**
-     * Creates a new DriverDataSource. If "driverClassName" is null, DriverDataSource will
-     * consult DriverManager for a registered driver for the given URL. So when specifying
-     * null, a user must take care of registering the driver. "connectionUrl" on the other
-     * hand must NOT be null.
-     */
-    public DriverDataSource(String driverClassName, String connectionUrl) {
-        this(driverClassName, connectionUrl, null, null);
-    }
-
-    /**
-     * Creates a new DriverDataSource. If "driverClassName" is null, DriverDataSource will
-     * consult DriverManager for a registered driver for the given URL. So when specifying
-     * null, a user must take care of registering the driver. "connectionUrl" on the other
-     * hand must NOT be null.
-     * 
-     * @since 3.0
-     */
-    public DriverDataSource(String driverClassName, String connectionUrl,
-            String userName, String password) {
-
-        setDriverClassName(driverClassName);
-
-        this.connectionUrl = connectionUrl;
-        this.userName = userName;
-        this.password = password;
-    }
-
-    /**
-     * Creates a new DriverDataSource wrapping a given Driver. If "driver" is null,
-     * DriverDataSource will consult DriverManager for a registered driver for the given
-     * URL. So when specifying null, a user must take care of registering the driver.
-     * "connectionUrl" on the other hand must NOT be null.
-     * 
-     * @since 1.1
-     */
-    public DriverDataSource(Driver driver, String connectionUrl, String userName,
-            String password) {
-
-        this._driver = driver;
-        this.driverClassName = driver.getClass().getName();
-        this.connectionUrl = connectionUrl;
-        this.userName = userName;
-        this.password = password;
-    }
-
-    /**
-     * Returns a new database connection, using preconfigured data to locate the database
-     * and obtain a connection.
-     */
-    public Connection getConnection() throws SQLException {
-        // login with internal credentials
-        return getConnection(userName, password);
-    }
-
-    /**
-     * Returns a new database connection using provided credentials to login to the
-     * database.
-     */
-    public Connection getConnection(String userName, String password) throws SQLException {
-        try {
-            if (logger != null) {
-                logger.logConnect(connectionUrl, userName, password);
-            }
-
-            Connection c = null;
-
-            if (getDriver() == null) {
-                c = DriverManager.getConnection(connectionUrl, userName, password);
-            }
-            else {
-                Properties connectProperties = new Properties();
-
-                if (userName != null) {
-                    connectProperties.put("user", userName);
-                }
-
-                if (password != null) {
-                    connectProperties.put("password", password);
-                }
-                c = getDriver().connect(connectionUrl, connectProperties);
-            }
-
-            // some drivers (Oracle) return null connections instead of throwing
-            // an exception... fix it here
-
-            if (c == null) {
-                throw new SQLException("Can't establish connection: " + connectionUrl);
-            }
-
-            if (logger != null) {
-                logger.logConnectSuccess();
-            }
-
-            return c;
-        }
-        catch (SQLException sqlex) {
-            if (logger != null) {
-                logger.logConnectFailure(sqlex);
-            }
-
-            throw sqlex;
-        }
-    }
-
-    public int getLoginTimeout() throws SQLException {
-        return -1;
-    }
-
-    public void setLoginTimeout(int seconds) throws SQLException {
-        // noop
-    }
-
-    public PrintWriter getLogWriter() throws SQLException {
-        return DriverManager.getLogWriter();
-    }
-
-    public void setLogWriter(PrintWriter out) throws SQLException {
-        DriverManager.setLogWriter(out);
-    }
-
-    public JdbcEventLogger getLogger() {
-        return logger;
-    }
-
-    public void setLogger(JdbcEventLogger delegate) {
-        logger = delegate;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String getConnectionUrl() {
-        return connectionUrl;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setConnectionUrl(String connectionUrl) {
-        this.connectionUrl = connectionUrl;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String getPassword() {
-        return password;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String getUserName() {
-        return userName;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setUserName(String userName) {
-        this.userName = userName;
-    }
-
-    public String getDriverClassName() {
-        return driverClassName;
-    }
-
-    public void setDriverClassName(String driverClassName) {
-        if (!Util.nullSafeEquals(getDriverClassName(), driverClassName)) {
-            this.driverClassName = driverClassName;
-            this._driver = null; // force reload
-        }
-    }
-    
-    /**
-     * Lazily instantiate the driver class to prevent errors for connections that are never opened
-     */
-    private Driver getDriver() throws SQLException {
-        if (_driver == null && driverClassName != null) {
-            _driver = loadDriver(driverClassName);
-        }
-        return _driver;
-    }
-    
-    /**
-     * @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/517df07b/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
deleted file mode 100644
index 357c3e0..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
+++ /dev/null
@@ -1,155 +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.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/517df07b/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
deleted file mode 100644
index 2ebfeab..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.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;
-
-/**
- * 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/517df07b/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
deleted file mode 100644
index eb327ff..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
+++ /dev/null
@@ -1,195 +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.sql.Connection;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.sql.ConnectionEvent;
-import javax.sql.ConnectionEventListener;
-import javax.sql.DataSource;
-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.
- */
-public class PooledConnectionImpl implements PooledConnection {
-
-	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/517df07b/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
deleted file mode 100644
index cccf905..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
+++ /dev/null
@@ -1,471 +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.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/517df07b/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
deleted file mode 100644
index 3066553..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
+++ /dev/null
@@ -1,64 +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;
-
-/**
- * A collection 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;
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/conn/package.html
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/package.html b/cayenne-server/src/main/java/org/apache/cayenne/conn/package.html
deleted file mode 100644
index 4fe2394..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/package.html
+++ /dev/null
@@ -1,26 +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.
--->
-<html>
-<body>
-Provides full DataSource implementation. Allows to wrap JDBC
-drivers into a DataSource-compatible classes. Adds features like connection 
-pooling, etc. Can be used as a DataSource implementation with application
-servers, web containers, etc.
-</body>
-</html>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/BadValidationQueryException.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/BadValidationQueryException.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/BadValidationQueryException.java
new file mode 100644
index 0000000..9f7e74b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/BadValidationQueryException.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.sql.SQLException;
+
+/**
+ * Thrown when the pool fails to validate a fresh connection that is known to be
+ * in a good state.
+ * 
+ * @since 4.0
+ */
+public class BadValidationQueryException extends SQLException {
+
+	private static final long serialVersionUID = -3690715196865727679L;
+
+	public BadValidationQueryException(String message) {
+		super(message);
+	}
+
+	public BadValidationQueryException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/DataSourceBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/DataSourceBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/DataSourceBuilder.java
new file mode 100644
index 0000000..e02f8eb
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/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.datasource;
+
+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 DataSourceBuilder validationQuery(String validationQuery) {
+		poolParameters.setValidationQuery(validationQuery);
+		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.");
+		}
+
+		return buildManaged(buildPooling(buildNonPooling()));
+	}
+
+	private DataSource buildNonPooling() {
+		Driver driver = objectFactory.newInstance(Driver.class, this.driver);
+		DriverDataSource dataSource = new DriverDataSource(driver, url, userName, password);
+		dataSource.setLogger(logger);
+		return dataSource;
+	}
+
+	private PoolingDataSource buildPooling(DataSource nonPoolingDataSource) {
+		return new PoolingDataSource(nonPoolingDataSource, poolParameters);
+	}
+
+	private DataSource buildManaged(PoolingDataSource dataSource) {
+		return new ManagedPoolingDataSource(dataSource);
+	}
+}


[2/3] cayenne git commit: CAY-2009 Non-blocking connection pool

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/DriverDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/DriverDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/DriverDataSource.java
new file mode 100644
index 0000000..af096dc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/DriverDataSource.java
@@ -0,0 +1,298 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.util.Util;
+
+/**
+ * A non-pooling DataSource implementation wrapping a JDBC driver.
+ * 
+ */
+public class DriverDataSource implements DataSource {
+
+    protected Driver _driver;
+    protected String driverClassName;
+    
+    protected String connectionUrl;
+    protected String userName;
+    protected String password;
+
+    protected JdbcEventLogger logger;
+
+    /**
+     * Loads JDBC driver using current thread class loader.
+     * 
+     * @since 3.0
+     * @deprecated since 4.0 as class loading should not happen here.
+     */
+    @Deprecated
+    private static Driver loadDriver(String driverClassName) throws SQLException {
+
+        Class<?> driverClass;
+        try {
+            driverClass = Util.getJavaClass(driverClassName);
+        }
+        catch (Exception ex) {
+            throw new SQLException("Can not load JDBC driver named '"
+                    + driverClassName
+                    + "': "
+                    + ex.getMessage());
+        }
+
+        try {
+            return (Driver) driverClass.newInstance();
+        }
+        catch (Exception ex) {
+            throw new SQLException("Error instantiating driver '"
+                    + driverClassName
+                    + "': "
+                    + ex.getMessage());
+        }
+    }
+
+    /**
+     * Creates a new DriverDataSource. If "driverClassName" is null, DriverDataSource will
+     * consult DriverManager for a registered driver for the given URL. So when specifying
+     * null, a user must take care of registering the driver. "connectionUrl" on the other
+     * hand must NOT be null.
+     */
+    public DriverDataSource(String driverClassName, String connectionUrl) {
+        this(driverClassName, connectionUrl, null, null);
+    }
+
+    /**
+     * Creates a new DriverDataSource. If "driverClassName" is null, DriverDataSource will
+     * consult DriverManager for a registered driver for the given URL. So when specifying
+     * null, a user must take care of registering the driver. "connectionUrl" on the other
+     * hand must NOT be null.
+     * 
+     * @since 3.0
+     */
+    public DriverDataSource(String driverClassName, String connectionUrl,
+            String userName, String password) {
+
+        setDriverClassName(driverClassName);
+
+        this.connectionUrl = connectionUrl;
+        this.userName = userName;
+        this.password = password;
+    }
+
+    /**
+     * Creates a new DriverDataSource wrapping a given Driver. If "driver" is null,
+     * DriverDataSource will consult DriverManager for a registered driver for the given
+     * URL. So when specifying null, a user must take care of registering the driver.
+     * "connectionUrl" on the other hand must NOT be null.
+     * 
+     * @since 1.1
+     */
+    public DriverDataSource(Driver driver, String connectionUrl, String userName,
+            String password) {
+
+        this._driver = driver;
+        this.driverClassName = driver.getClass().getName();
+        this.connectionUrl = connectionUrl;
+        this.userName = userName;
+        this.password = password;
+    }
+
+    /**
+     * Returns a new database connection, using preconfigured data to locate the database
+     * and obtain a connection.
+     */
+    public Connection getConnection() throws SQLException {
+        // login with internal credentials
+        return getConnection(userName, password);
+    }
+
+    /**
+     * Returns a new database connection using provided credentials to login to the
+     * database.
+     */
+    public Connection getConnection(String userName, String password) throws SQLException {
+        try {
+            if (logger != null) {
+                logger.logConnect(connectionUrl, userName, password);
+            }
+
+            Connection c = null;
+
+            if (getDriver() == null) {
+                c = DriverManager.getConnection(connectionUrl, userName, password);
+            }
+            else {
+                Properties connectProperties = new Properties();
+
+                if (userName != null) {
+                    connectProperties.put("user", userName);
+                }
+
+                if (password != null) {
+                    connectProperties.put("password", password);
+                }
+                c = getDriver().connect(connectionUrl, connectProperties);
+            }
+
+            // some drivers (Oracle) return null connections instead of throwing
+            // an exception... fix it here
+
+            if (c == null) {
+                throw new SQLException("Can't establish connection: " + connectionUrl);
+            }
+
+            if (logger != null) {
+                logger.logConnectSuccess();
+            }
+
+            return c;
+        }
+        catch (SQLException sqlex) {
+            if (logger != null) {
+                logger.logConnectFailure(sqlex);
+            }
+
+            throw sqlex;
+        }
+    }
+
+    public int getLoginTimeout() throws SQLException {
+        return -1;
+    }
+
+    public void setLoginTimeout(int seconds) throws SQLException {
+        // noop
+    }
+
+    public PrintWriter getLogWriter() throws SQLException {
+        return DriverManager.getLogWriter();
+    }
+
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        DriverManager.setLogWriter(out);
+    }
+
+    public JdbcEventLogger getLogger() {
+        return logger;
+    }
+
+    public void setLogger(JdbcEventLogger delegate) {
+        logger = delegate;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public String getConnectionUrl() {
+        return connectionUrl;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setConnectionUrl(String connectionUrl) {
+        this.connectionUrl = connectionUrl;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public String getUserName() {
+        return userName;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getDriverClassName() {
+        return driverClassName;
+    }
+
+    public void setDriverClassName(String driverClassName) {
+        if (!Util.nullSafeEquals(getDriverClassName(), driverClassName)) {
+            this.driverClassName = driverClassName;
+            this._driver = null; // force reload
+        }
+    }
+    
+    /**
+     * Lazily instantiate the driver class to prevent errors for connections that are never opened
+     */
+    private Driver getDriver() throws SQLException {
+        if (_driver == null && driverClassName != null) {
+            _driver = loadDriver(driverClassName);
+        }
+        return _driver;
+    }
+    
+    /**
+     * @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/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
new file mode 100644
index 0000000..65083b1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   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.datasource;
+
+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.
+ * 
+ * @since 4.0
+ */
+public class ManagedPoolingDataSource implements DataSource, ScopeEventListener {
+
+	private PoolingDataSourceManager dataSourceManager;
+	private DataSource dataSource;
+
+	public ManagedPoolingDataSource(PoolingDataSource dataSource) {
+
+		this.dataSource = dataSource;
+		this.dataSourceManager = new PoolingDataSourceManager(dataSource);
+
+		dataSourceManager.start();
+	}
+
+	@Override
+	public void beforeScopeEnd() {
+
+		// swap the underlying DataSource to prevent further interaction with
+		// the callers
+		this.dataSource = new StoppedDataSource(dataSource);
+
+		// shut down the thread..
+		dataSourceManager.shutdown();
+	}
+
+	@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 SQLFeatureNotSupportedException();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolAwareConnection.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolAwareConnection.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolAwareConnection.java
new file mode 100644
index 0000000..6b90504
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolAwareConnection.java
@@ -0,0 +1,609 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link Connection} wrapper that interacts with the
+ * {@link PoolingDataSource}, allowing to recycle connections and track
+ * failures.
+ * 
+ * @since 4.0
+ */
+public class PoolAwareConnection implements Connection {
+
+	private PoolingDataSource parent;
+	private Connection connection;
+	private String validationQuery;
+
+	// An old hack that fixes Sybase problems with autocommit. Used idea from
+	// Jonas org.objectweb.jonas.jdbc_xa.ConnectionImpl
+	// (http://www.objectweb.org/jonas/).
+	//
+	// If problem is not the one that can be fixed by this patch, original
+	// exception is rethrown. If exception occurs when fixing the problem, new
+	// exception is thrown.
+	//
+	static void sybaseAutoCommitPatch(Connection c, SQLException e, boolean autoCommit) throws SQLException {
+
+		String s = e.getMessage().toLowerCase();
+		if (s.contains("set chained command not allowed")) {
+			c.commit();
+			c.setAutoCommit(autoCommit); // Shouldn't fail now.
+		} else {
+			throw e;
+		}
+	}
+
+	public PoolAwareConnection(PoolingDataSource parent, Connection connection, String validationQuery) {
+		this.parent = parent;
+		this.connection = connection;
+		this.validationQuery = validationQuery;
+	}
+
+	Connection getConnection() {
+		return connection;
+	}
+
+	boolean validate() {
+
+		if (validationQuery == null) {
+			return true;
+		}
+
+		try {
+			Statement statement = connection.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();
+			}
+		} catch (SQLException e) {
+			return false;
+		}
+
+		return true;
+	}
+
+	void recover(SQLException reconnectCause) throws SQLException {
+
+		try {
+			connection.close();
+		} catch (SQLException e) {
+			// ignore exception, since connection is expected to be in a bad
+			// state
+		}
+
+		try {
+			connection = parent.createUnwrapped();
+		} catch (SQLException e) {
+			parent.retire(this);
+			throw reconnectCause;
+		}
+	}
+
+	@Override
+	public void clearWarnings() throws SQLException {
+		try {
+			connection.clearWarnings();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void close() throws SQLException {
+		parent.reclaim(this);
+	}
+
+	@Override
+	public void commit() throws SQLException {
+		try {
+			connection.commit();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public Statement createStatement() throws SQLException {
+		try {
+			return connection.createStatement();
+		} catch (SQLException sqlEx) {
+			recover(sqlEx);
+			return connection.createStatement();
+		}
+	}
+
+	@Override
+	public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+		try {
+			return connection.createStatement(resultSetType, resultSetConcurrency);
+		} catch (SQLException e) {
+			recover(e);
+			return connection.createStatement(resultSetType, resultSetConcurrency);
+		}
+	}
+
+	@Override
+	public boolean getAutoCommit() throws SQLException {
+		try {
+			return connection.getAutoCommit();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public String getCatalog() throws SQLException {
+		try {
+			return connection.getCatalog();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public DatabaseMetaData getMetaData() throws SQLException {
+		try {
+			return connection.getMetaData();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public int getTransactionIsolation() throws SQLException {
+		try {
+			return connection.getTransactionIsolation();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public SQLWarning getWarnings() throws SQLException {
+		try {
+			return connection.getWarnings();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public boolean isClosed() throws SQLException {
+
+		try {
+			return connection.isClosed();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public boolean isReadOnly() throws SQLException {
+		try {
+			return connection.isReadOnly();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public String nativeSQL(String sql) throws SQLException {
+		try {
+			return connection.nativeSQL(sql);
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public CallableStatement prepareCall(String sql) throws SQLException {
+		try {
+			return connection.prepareCall(sql);
+		} catch (SQLException sqlEx) {
+			recover(sqlEx);
+			return connection.prepareCall(sql);
+		}
+	}
+
+	@Override
+	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+		try {
+			return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
+		}
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql) throws SQLException {
+		try {
+			return connection.prepareStatement(sql);
+		} catch (SQLException sqlEx) {
+			recover(sqlEx);
+			return connection.prepareStatement(sql);
+		}
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+			throws SQLException {
+		try {
+			return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
+		}
+	}
+
+	@Override
+	public void rollback() throws SQLException {
+		try {
+			connection.rollback();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void setAutoCommit(boolean autoCommit) throws SQLException {
+		try {
+			connection.setAutoCommit(autoCommit);
+		} catch (SQLException sqlEx) {
+
+			try {
+				// apply Sybase patch
+				sybaseAutoCommitPatch(connection, sqlEx, autoCommit);
+			} catch (SQLException patchEx) {
+				parent.retire(this);
+				throw sqlEx;
+			}
+		}
+	}
+
+	@Override
+	public void setCatalog(String catalog) throws SQLException {
+		try {
+			connection.setCatalog(catalog);
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void setReadOnly(boolean readOnly) throws SQLException {
+		try {
+			connection.setReadOnly(readOnly);
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void setTransactionIsolation(int level) throws SQLException {
+		try {
+			connection.setTransactionIsolation(level);
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public Map<String, Class<?>> getTypeMap() throws SQLException {
+		try {
+			return connection.getTypeMap();
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+		try {
+			connection.setTypeMap(map);
+		} catch (SQLException sqlEx) {
+			parent.retire(this);
+			throw sqlEx;
+		}
+	}
+
+	@Override
+	public void setHoldability(int holdability) throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method setHoldability() not yet implemented.");
+	}
+
+	@Override
+	public int getHoldability() throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method getHoldability() not yet implemented.");
+	}
+
+	@Override
+	public Savepoint setSavepoint() throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method setSavepoint() not yet implemented.");
+	}
+
+	@Override
+	public Savepoint setSavepoint(String name) throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method setSavepoint() not yet implemented.");
+	}
+
+	@Override
+	public void rollback(Savepoint savepoint) throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method rollback() not yet implemented.");
+	}
+
+	@Override
+	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method releaseSavepoint() not yet implemented.");
+	}
+
+	@Override
+	public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+			throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method createStatement() not yet implemented.");
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
+			int resultSetHoldability) throws SQLException {
+		throw new java.lang.UnsupportedOperationException("Method prepareStatement() not yet implemented.");
+	}
+
+	@Override
+	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+			int resultSetHoldability) throws SQLException {
+		try {
+			return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+		} catch (SQLException e) {
+
+			recover(e);
+			return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+		}
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+
+		try {
+			return connection.prepareStatement(sql, autoGeneratedKeys);
+		} catch (SQLException e) {
+
+			recover(e);
+			return connection.prepareStatement(sql, autoGeneratedKeys);
+		}
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+		try {
+			return connection.prepareStatement(sql, columnIndexes);
+		} catch (SQLException e) {
+
+			recover(e);
+			return connection.prepareStatement(sql, columnIndexes);
+		}
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+		try {
+			return connection.prepareStatement(sql, columnNames);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.prepareStatement(sql, columnNames);
+		}
+	}
+
+	@Override
+	public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+		try {
+			return connection.createArrayOf(typeName, elements);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createArrayOf(typeName, elements);
+		}
+	}
+
+	@Override
+	public Blob createBlob() throws SQLException {
+		try {
+			return connection.createBlob();
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createBlob();
+		}
+	}
+
+	@Override
+	public Clob createClob() throws SQLException {
+		try {
+			return connection.createClob();
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createClob();
+		}
+	}
+
+	@Override
+	public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+		try {
+			return connection.createStruct(typeName, attributes);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createStruct(typeName, attributes);
+		}
+	}
+
+	@Override
+	public Properties getClientInfo() throws SQLException {
+		try {
+			return connection.getClientInfo();
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.getClientInfo();
+		}
+	}
+
+	@Override
+	public String getClientInfo(String name) throws SQLException {
+		try {
+			return connection.getClientInfo(name);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.getClientInfo(name);
+		}
+	}
+
+	@Override
+	public boolean isValid(int timeout) throws SQLException {
+		try {
+			return connection.isValid(timeout);
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.isValid(timeout);
+		}
+	}
+
+	@Override
+	public boolean isWrapperFor(Class<?> iface) throws SQLException {
+		return (PoolAwareConnection.class.equals(iface)) ? true : connection.isWrapperFor(iface);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T unwrap(Class<T> iface) throws SQLException {
+		return PoolAwareConnection.class.equals(iface) ? (T) this : connection.unwrap(iface);
+	}
+
+	@Override
+	public NClob createNClob() throws SQLException {
+		try {
+			return connection.createNClob();
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createNClob();
+		}
+	}
+
+	@Override
+	public SQLXML createSQLXML() throws SQLException {
+		try {
+			return connection.createSQLXML();
+		} catch (SQLException sqlEx) {
+
+			recover(sqlEx);
+			return connection.createSQLXML();
+		}
+	}
+
+	@Override
+	public void setClientInfo(Properties properties) throws SQLClientInfoException {
+		// TODO: we can implement that now.
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void setClientInfo(String name, String value) throws SQLClientInfoException {
+		// TODO: we can implement that now.
+		throw new UnsupportedOperationException();
+	}
+
+	// JDBC 4.1 compatibility pre Java 7
+	public void setSchema(String schema) throws SQLException {
+		throw new UnsupportedOperationException();
+	}
+
+	// JDBC 4.1 compatibility pre Java 7
+	public String getSchema() throws SQLException {
+		throw new UnsupportedOperationException();
+	}
+
+	// JDBC 4.1 compatibility pre Java 7
+	public void abort(Executor executor) throws SQLException {
+		throw new UnsupportedOperationException();
+	}
+
+	// JDBC 4.1 compatibility pre Java 7
+	public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
+		throw new UnsupportedOperationException();
+	}
+
+	// JDBC 4.1 compatibility pre Java 7
+	public int getNetworkTimeout() throws SQLException {
+		throw new UnsupportedOperationException();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
new file mode 100644
index 0000000..83bf2f2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
@@ -0,0 +1,314 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A non-blocking {@link DataSource} with a pool of connections.
+ * 
+ * @since 4.0
+ */
+public class PoolingDataSource implements DataSource {
+
+	/**
+	 * 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;
+
+	private static Log LOGGER = LogFactory.getLog(PoolingDataSource.class);
+
+	private DataSource nonPoolingDataSource;
+	private long maxQueueWaitTime;
+
+	private Map<PoolAwareConnection, Object> pool;
+	private Semaphore poolCap;
+	private BlockingQueue<PoolAwareConnection> available;
+
+	private int maxIdleConnections;
+	private int minConnections;
+	private String validationQuery;
+
+	static int maxIdleConnections(int min, int max) {
+		return min == max ? min : min + (int) Math.ceil((max - min) / 2d);
+	}
+
+	public PoolingDataSource(DataSource nonPoolingDataSource, PoolingDataSourceParameters parameters) {
+
+		int minConnections = parameters.getMinConnections();
+		int maxConnections = parameters.getMaxConnections();
+
+		// sanity check
+		if (minConnections < 0) {
+			throw new IllegalArgumentException("Negative min connections: " + minConnections);
+		}
+
+		if (maxConnections < 0) {
+			throw new IllegalArgumentException("Negative max connections: " + maxConnections);
+		}
+
+		if (minConnections > maxConnections) {
+			throw new IllegalArgumentException("Min connections (" + minConnections
+					+ ") is greater than max connections (" + maxConnections + ")");
+		}
+
+		this.nonPoolingDataSource = nonPoolingDataSource;
+		this.maxQueueWaitTime = parameters.getMaxQueueWaitTime();
+		this.validationQuery = parameters.getValidationQuery();
+		this.minConnections = minConnections;
+		this.pool = new ConcurrentHashMap<PoolAwareConnection, Object>((int) (maxConnections / 0.75));
+		this.available = new ArrayBlockingQueue<PoolAwareConnection>(maxConnections);
+		this.poolCap = new Semaphore(maxConnections);
+		this.maxIdleConnections = maxIdleConnections(minConnections, maxConnections);
+
+		// grow pull to min connections
+		try {
+			for (int i = 0; i < minConnections; i++) {
+				PoolAwareConnection c = createUnchecked();
+				reclaim(c);
+			}
+		} catch (BadValidationQueryException e) {
+			throw new CayenneRuntimeException("Bad validation query: " + validationQuery, e);
+		} catch (SQLException e) {
+			LOGGER.info("Error creating new connection when starting connection pool, ignoring", e);
+		}
+	}
+
+	int poolSize() {
+		return pool.size();
+	}
+
+	int availableSize() {
+		return available.size();
+	}
+
+	void shutdown() {
+
+		// expecting surrounding environment to block new requests for
+		// connections before calling this method. Still previously unchecked
+		// connections may be returned. I.e. "pool" will not grow during
+		// shutdown, which is the only thing that we need
+
+		for (PoolAwareConnection c : pool.keySet()) {
+			retire(c);
+		}
+
+		available.clear();
+		pool = Collections.emptyMap();
+	}
+
+	void managePool() {
+
+		// do not grow or shrink abruptly ... open or close one connection on
+		// each call
+
+		if (available.size() < minConnections) {
+
+			try {
+				PoolAwareConnection c = createUnchecked();
+				reclaim(c);
+			} catch (SQLException e) {
+				LOGGER.info("Error creating new connection when managing connection pool, ignoring", e);
+			}
+
+		} else if (available.size() > maxIdleConnections) {
+
+			PoolAwareConnection c = uncheckNonBlocking(false);
+			if (c != null) {
+				retire(c);
+			}
+		}
+
+	}
+
+	/**
+	 * Closes the connection and removes it from the pool. The connection must
+	 * be an unchecked connection.
+	 */
+	void retire(PoolAwareConnection connection) {
+		pool.remove(connection);
+
+		poolCap.release();
+
+		try {
+			connection.getConnection().close();
+		} catch (SQLException e) {
+			// ignore?
+		}
+	}
+
+	/**
+	 * Returns connection back to the pool if possible. The connection must be
+	 * an unchecked connection.
+	 */
+	void reclaim(PoolAwareConnection connection) {
+
+		// the queue may overflow potentially and we won't be able to add the
+		// object
+		if (!available.offer(connection)) {
+			retire(connection);
+		}
+	}
+
+	PoolAwareConnection uncheckNonBlocking(boolean validate) {
+		PoolAwareConnection c = available.poll();
+		return validate ? validateUnchecked(c) : c;
+	}
+
+	PoolAwareConnection uncheckBlocking(boolean validate) {
+		PoolAwareConnection c;
+		try {
+			c = available.poll(maxQueueWaitTime, TimeUnit.MILLISECONDS);
+		} catch (InterruptedException e) {
+			return null;
+		}
+
+		return validate ? validateUnchecked(c) : c;
+	}
+
+	PoolAwareConnection validateUnchecked(PoolAwareConnection c) {
+
+		if (c == null || c.validate()) {
+			return c;
+		}
+
+		// this will recursively validate all connections that exist in the pool
+		// until a valid one is found or a pool is exhausted
+		retire(c);
+		return validateUnchecked(available.poll());
+	}
+
+	PoolAwareConnection createUnchecked() throws SQLException {
+
+		if (!poolCap.tryAcquire()) {
+			return null;
+		}
+
+		PoolAwareConnection c = createWrapped();
+
+		pool.put(c, 1);
+
+		// even though we got a fresh connection, let's still validate it...
+		// This will provide consistent behavior between cached and uncached
+		// connections in respect to invalid validation queries
+		if (!c.validate()) {
+			throw new BadValidationQueryException(
+					"Can't validate a fresh connection. Likely validation query is wrong: " + validationQuery);
+		}
+
+		return c;
+	}
+
+	PoolAwareConnection createWrapped() throws SQLException {
+		return new PoolAwareConnection(this, createUnwrapped(), validationQuery);
+	}
+
+	Connection createUnwrapped() throws SQLException {
+		return nonPoolingDataSource.getConnection();
+	}
+
+	@Override
+	public Connection getConnection() throws SQLException {
+
+		// strategy for getting a connection -
+		// 1. quick peek for available connections
+		// 2. create new one
+		// 3. wait for a user to return connection
+
+		PoolAwareConnection c;
+
+		c = uncheckNonBlocking(true);
+		if (c != null) {
+			return c;
+		}
+
+		c = createUnchecked();
+		if (c != null) {
+			return c;
+		}
+
+		c = uncheckBlocking(true);
+		if (c != null) {
+			return c;
+		}
+
+		throw new SQLException("Can't obtain connection. Request to pool timed out. Total pool size: " + pool.size());
+	}
+
+	@Override
+	public Connection getConnection(String userName, String password) throws SQLException {
+		throw new UnsupportedOperationException(
+				"Connections for a specific user are not supported by the pooled DataSource");
+	}
+
+	@Override
+	public int getLoginTimeout() throws java.sql.SQLException {
+		return nonPoolingDataSource.getLoginTimeout();
+	}
+
+	@Override
+	public void setLoginTimeout(int seconds) throws java.sql.SQLException {
+		nonPoolingDataSource.setLoginTimeout(seconds);
+	}
+
+	@Override
+	public PrintWriter getLogWriter() throws java.sql.SQLException {
+		return nonPoolingDataSource.getLogWriter();
+	}
+
+	@Override
+	public void setLogWriter(PrintWriter out) throws java.sql.SQLException {
+		nonPoolingDataSource.setLogWriter(out);
+	}
+
+	@Override
+	public boolean isWrapperFor(Class<?> iface) throws SQLException {
+		return (PoolingDataSource.class.equals(iface)) ? true : nonPoolingDataSource.isWrapperFor(iface);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T unwrap(Class<T> iface) throws SQLException {
+		return PoolingDataSource.class.equals(iface) ? (T) this : nonPoolingDataSource.unwrap(iface);
+	}
+
+	// JDBC 4.1 compatibility under Java pre 1.7
+	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+		throw new SQLFeatureNotSupportedException();
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceManager.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceManager.java
new file mode 100644
index 0000000..6dacdd6
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceManager.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ *   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.datasource;
+
+/**
+ * Manages the state of a {@link PoolingDataSource} instance, performing
+ * periodic expansion/contraction of pooled connections, and orchestrating
+ * shutdown.
+ * 
+ * @since 4.0
+ */
+class PoolingDataSourceManager extends Thread {
+
+	private volatile boolean shouldStop;
+	private PoolingDataSource dataSource;
+
+	PoolingDataSourceManager(PoolingDataSource dataSource) {
+		setName("PoolManagerCleanup-" + dataSource.hashCode());
+		setDaemon(true);
+
+		this.dataSource = dataSource;
+		this.shouldStop = false;
+	}
+
+	public void shutdown() {
+		shouldStop = true;
+		dataSource.shutdown();
+		interrupt();
+	}
+
+	@Override
+	public void run() {
+		while (true) {
+
+			try {
+				// don't do it too often
+				Thread.sleep(600000);
+			} catch (InterruptedException iex) {
+				// ignore...
+			}
+
+			if (shouldStop) {
+				break;
+			}
+
+			dataSource.managePool();
+		}
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceParameters.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceParameters.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSourceParameters.java
new file mode 100644
index 0000000..545155b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/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.datasource;
+
+/**
+ * A collection 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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/main/java/org/apache/cayenne/datasource/StoppedDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/StoppedDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/StoppedDataSource.java
new file mode 100644
index 0000000..383c7b7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/StoppedDataSource.java
@@ -0,0 +1,88 @@
+/*****************************************************************
+ *   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.datasource;
+
+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;
+
+/**
+ * A DataSource wrapper that prevents callers from obtaining connections from
+ * another {@link DataSource}.
+ * 
+ * @since 4.0
+ */
+class StoppedDataSource implements DataSource {
+
+	private DataSource dataSource;
+
+	public StoppedDataSource(DataSource dataSource) {
+		this.dataSource = dataSource;
+	}
+
+	@Override
+	public Connection getConnection() throws SQLException {
+		throw new SQLException("DataSource is stopped");
+	}
+
+	@Override
+	public Connection getConnection(String arg0, String arg1) throws SQLException {
+		throw new SQLException("DataSource is stopped");
+	}
+
+	@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 (StoppedDataSource.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 StoppedDataSource.class.equals(iface) ? (T) this : dataSource.unwrap(iface);
+	}
+
+	// JDBC 4.1 compatibility under Java pre 1.7
+	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+		throw new SQLFeatureNotSupportedException();
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/BasePoolingDataSourceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/BasePoolingDataSourceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/BasePoolingDataSourceIT.java
deleted file mode 100644
index ef81d1a..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/BasePoolingDataSourceIT.java
+++ /dev/null
@@ -1,74 +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.sql.Driver;
-import java.sql.SQLException;
-
-import org.apache.cayenne.di.AdhocObjectFactory;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.After;
-import org.junit.Before;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class BasePoolingDataSourceIT extends ServerCase {
-
-	@Inject
-	private DataSourceInfo dataSourceInfo;
-
-	@Inject
-	private AdhocObjectFactory objectFactory;
-
-	@Inject
-	private JdbcEventLogger logger;
-
-	protected PoolingDataSource dataSource;
-
-	@Before
-	public void before() throws SQLException {
-
-		Driver driver = objectFactory.newInstance(Driver.class, dataSourceInfo.getJdbcDriver());
-		DriverDataSource nonPooling = new DriverDataSource(driver, dataSourceInfo.getDataSourceUrl(),
-				dataSourceInfo.getUserName(), dataSourceInfo.getPassword());
-		nonPooling.setLogger(logger);
-
-		PoolingDataSourceParameters poolParameters = createParameters();
-		this.dataSource = new PoolingDataSource(new PooledConnectionFactory(nonPooling), poolParameters);
-	}
-
-	@After
-	public void after() throws SQLException {
-		if (dataSource != null) {
-			dataSource.beforeScopeEnd();
-		}
-	}
-
-	protected PoolingDataSourceParameters createParameters() {
-		PoolingDataSourceParameters poolParameters = new PoolingDataSourceParameters();
-		poolParameters.setMinConnections(2);
-		poolParameters.setMaxConnections(3);
-		poolParameters.setMaxQueueWaitTime(1000);
-		return poolParameters;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/DriverDataSourceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/DriverDataSourceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/DriverDataSourceTest.java
deleted file mode 100644
index bd3ecd7..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/DriverDataSourceTest.java
+++ /dev/null
@@ -1,43 +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 org.junit.Test;
-
-import java.sql.SQLException;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-public class DriverDataSourceTest {
-
-    @Test
-    public void testLazyInstantiationOfDriverClass() {
-        DriverDataSource dataSource = new DriverDataSource("does.not.exist.Driver", "jdbc:postgresql://localhost/database");
-        assertNotNull(dataSource);
-        
-        try {
-            dataSource.getConnection();
-            fail();
-        } catch (SQLException e) {
-            // expected because driver class does not exist
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java
deleted file mode 100644
index 829ceb6..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java
+++ /dev/null
@@ -1,67 +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 static org.mockito.Mockito.mock;
-
-import java.sql.SQLException;
-
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class PooledConnectionImplTest {
-
-	private PoolingDataSource dataSource;
-
-	@Before
-	public void before() throws SQLException {
-		PoolingDataSourceParameters poolParameters = new PoolingDataSourceParameters();
-		poolParameters.setMinConnections(0);
-		poolParameters.setMaxConnections(0);
-		poolParameters.setMaxQueueWaitTime(1000);
-
-		this.dataSource = new PoolingDataSource(mock(ConnectionPoolDataSource.class), poolParameters);
-	}
-
-	@Test
-	public void testConnectionErrorNotificationConcurrency() throws Exception {
-		// test a case when error notification is sent to connection
-		// that has been removed from the pool, but when pool is still a
-		// listener for its events.
-
-		PooledConnectionImpl con = new PooledConnectionImpl(mock(DataSource.class), "un", "password");
-		con.addConnectionEventListener(dataSource);
-		con.connectionErrorNotification(new java.sql.SQLException("Bad SQL Exception.."));
-	}
-
-	@Test
-	public void testConnectionClosedNotificationConcurrency() throws Exception {
-		// test a case when closed notification is sent to connection
-		// that has been removed from the pool, but when pool is still a
-		// listener for its events.
-		PooledConnectionImpl con = new PooledConnectionImpl(mock(DataSource.class), "un", "password");
-		con.addConnectionEventListener(dataSource);
-		con.connectionClosedNotification();
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSourceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSourceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSourceIT.java
deleted file mode 100644
index d2814d9..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSourceIT.java
+++ /dev/null
@@ -1,72 +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 static org.junit.Assert.assertEquals;
-
-import java.sql.Connection;
-
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class PoolingDataSourceIT extends BasePoolingDataSourceIT {
-
-	@Test(expected = UnsupportedOperationException.class)
-	public void testGetConnectionWithUserName() throws Exception {
-		dataSource.getConnection("user", "password");
-	}
-
-	@Test
-	public void testGetConnection() throws Exception {
-
-		assertEquals(0, dataSource.getCurrentlyInUse());
-		assertEquals(2, dataSource.getCurrentlyUnused());
-
-		Connection c1 = dataSource.getConnection();
-		assertEquals(1, dataSource.getCurrentlyInUse());
-		assertEquals(1, dataSource.getCurrentlyUnused());
-
-		Connection c2 = dataSource.getConnection();
-		assertEquals(2, dataSource.getCurrentlyInUse());
-		assertEquals(0, dataSource.getCurrentlyUnused());
-
-		c1.close();
-		assertEquals(1, dataSource.getCurrentlyInUse());
-		assertEquals(1, dataSource.getCurrentlyUnused());
-
-		c2.close();
-		assertEquals(0, dataSource.getCurrentlyInUse());
-		assertEquals(2, dataSource.getCurrentlyUnused());
-	}
-
-	@Test
-	public void testGetConnection_BeforeScopeEnd() throws Exception {
-
-		assertEquals(0, dataSource.getCurrentlyInUse());
-		assertEquals(2, dataSource.getCurrentlyUnused());
-
-		dataSource.beforeScopeEnd();
-
-		assertEquals(0, dataSource.getCurrentlyInUse());
-		assertEquals(0, dataSource.getCurrentlyUnused());
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_FailingValidationQueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_FailingValidationQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_FailingValidationQueryIT.java
deleted file mode 100644
index d832ae5..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_FailingValidationQueryIT.java
+++ /dev/null
@@ -1,43 +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.sql.Connection;
-import java.sql.SQLException;
-
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class PoolingDataSource_FailingValidationQueryIT extends BasePoolingDataSourceIT {
-
-	@Override
-	protected PoolingDataSourceParameters createParameters() {
-		PoolingDataSourceParameters params = super.createParameters();
-		params.setValidationQuery("SELECT count(1) FROM NO_SUCH_TABLE");
-		return params;
-	}
-
-	@Test(expected = SQLException.class)
-	public void testGetConnection_ValidationQuery() throws Exception {
-		Connection c1 = dataSource.getConnection();
-		c1.close();
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_ValidationQueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_ValidationQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_ValidationQueryIT.java
deleted file mode 100644
index 45a5689..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_ValidationQueryIT.java
+++ /dev/null
@@ -1,55 +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 static org.junit.Assert.assertEquals;
-
-import java.sql.Connection;
-
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class PoolingDataSource_ValidationQueryIT extends BasePoolingDataSourceIT {
-
-	@Override
-	protected PoolingDataSourceParameters createParameters() {
-		PoolingDataSourceParameters params = super.createParameters();
-		params.setValidationQuery("SELECT count(1) FROM ARTIST");
-		return params;
-	}
-
-	@Test
-	public void testGetConnection_ValidationQuery() throws Exception {
-
-		assertEquals(0, dataSource.getCurrentlyInUse());
-		assertEquals(2, dataSource.getCurrentlyUnused());
-
-		// TODO: we are not testing much here... we really need to mock
-		// validation query execution somehow and verify that it is taken into
-		// account
-		
-		Connection c1 = dataSource.getConnection();
-		assertEquals(1, dataSource.getCurrentlyInUse());
-		assertEquals(1, dataSource.getCurrentlyUnused());
-
-		c1.close();
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/BasePoolingDataSourceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/BasePoolingDataSourceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/BasePoolingDataSourceIT.java
new file mode 100644
index 0000000..0c9ea45
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/BasePoolingDataSourceIT.java
@@ -0,0 +1,77 @@
+/*****************************************************************
+ *   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.datasource;
+
+import java.sql.Driver;
+import java.sql.SQLException;
+
+import org.apache.cayenne.conn.DataSourceInfo;
+import org.apache.cayenne.datasource.PoolingDataSource;
+import org.apache.cayenne.datasource.PoolingDataSourceParameters;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.After;
+import org.junit.Before;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class BasePoolingDataSourceIT extends ServerCase {
+
+	@Inject
+	private DataSourceInfo dataSourceInfo;
+
+	@Inject
+	private AdhocObjectFactory objectFactory;
+
+	@Inject
+	private JdbcEventLogger logger;
+
+	protected PoolingDataSource dataSource;
+
+	@Before
+	public void before() throws SQLException {
+
+		Driver driver = objectFactory.newInstance(Driver.class, dataSourceInfo.getJdbcDriver());
+		DriverDataSource nonPooling = new DriverDataSource(driver, dataSourceInfo.getDataSourceUrl(),
+				dataSourceInfo.getUserName(), dataSourceInfo.getPassword());
+		nonPooling.setLogger(logger);
+
+		PoolingDataSourceParameters poolParameters = createParameters();
+		this.dataSource = new PoolingDataSource(nonPooling, poolParameters);
+	}
+
+	@After
+	public void after() throws SQLException {
+		if (dataSource != null) {
+			dataSource.shutdown();
+		}
+	}
+
+	protected PoolingDataSourceParameters createParameters() {
+		PoolingDataSourceParameters poolParameters = new PoolingDataSourceParameters();
+		poolParameters.setMinConnections(2);
+		poolParameters.setMaxConnections(3);
+		poolParameters.setMaxQueueWaitTime(1000);
+		return poolParameters;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/DriverDataSourceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/DriverDataSourceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/DriverDataSourceTest.java
new file mode 100644
index 0000000..cb361f2
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/DriverDataSourceTest.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.datasource;
+
+import org.apache.cayenne.datasource.DriverDataSource;
+import org.junit.Test;
+
+import java.sql.SQLException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class DriverDataSourceTest {
+
+    @Test
+    public void testLazyInstantiationOfDriverClass() {
+        DriverDataSource dataSource = new DriverDataSource("does.not.exist.Driver", "jdbc:postgresql://localhost/database");
+        assertNotNull(dataSource);
+        
+        try {
+            dataSource.getConnection();
+            fail();
+        } catch (SQLException e) {
+            // expected because driver class does not exist
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolAwareConnectionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolAwareConnectionTest.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolAwareConnectionTest.java
new file mode 100644
index 0000000..da94b94
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolAwareConnectionTest.java
@@ -0,0 +1,99 @@
+/*****************************************************************
+ *   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.datasource;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.apache.cayenne.datasource.PoolAwareConnection;
+import org.apache.cayenne.datasource.PoolingDataSource;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PoolAwareConnectionTest {
+
+	private PoolingDataSource parentMock;
+	private Connection connectionMock;
+
+	@Before
+	public void before() throws SQLException {
+		connectionMock = mock(Connection.class);
+
+		parentMock = mock(PoolingDataSource.class);
+		when(parentMock.createUnwrapped()).thenReturn(connectionMock);
+	}
+
+	@Test
+	public void testRecover() throws SQLException {
+		PoolAwareConnection paConnection = new PoolAwareConnection(parentMock, connectionMock, null);
+		SQLException e = mock(SQLException.class);
+
+		verify(parentMock, times(0)).createUnwrapped();
+		paConnection.recover(e);
+		verify(parentMock, times(1)).createUnwrapped();
+
+		assertSame(connectionMock, paConnection.getConnection());
+	}
+
+	@Test
+	public void testPrepareStatement() throws SQLException {
+		PreparedStatement firstTry = mock(PreparedStatement.class);
+		PreparedStatement secondTry = mock(PreparedStatement.class);
+
+		when(connectionMock.prepareStatement(anyString())).thenReturn(firstTry, secondTry);
+
+		PoolAwareConnection paConnection = new PoolAwareConnection(parentMock, connectionMock, null);
+		PreparedStatement st = paConnection.prepareStatement("SELECT 1");
+		assertSame(firstTry, st);
+	}
+
+	@Test
+	public void testPrepareStatement_Recover() throws SQLException {
+
+		PreparedStatement secondTry = mock(PreparedStatement.class);
+		when(connectionMock.prepareStatement(anyString())).thenThrow(new SQLException("E1")).thenReturn(secondTry);
+
+		PoolAwareConnection paConnection = new PoolAwareConnection(parentMock, connectionMock, null);
+		PreparedStatement st = paConnection.prepareStatement("SELECT 1");
+		assertSame(secondTry, st);
+	}
+
+	@Test
+	public void testPrepareStatement_Recover_Impossible() throws SQLException {
+
+		SQLException original = new SQLException("E1");
+		when(connectionMock.prepareStatement(anyString())).thenThrow(original);
+		PoolAwareConnection paConnection = new PoolAwareConnection(parentMock, connectionMock, null);
+
+		try {
+			paConnection.prepareStatement("SELECT 1");
+		} catch (SQLException e) {
+			assertSame(original, e);
+		}
+
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceIT.java
new file mode 100644
index 0000000..e9b0746
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceIT.java
@@ -0,0 +1,160 @@
+/*****************************************************************
+ *   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.datasource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class PoolingDataSourceIT extends BasePoolingDataSourceIT {
+
+	@Test(expected = UnsupportedOperationException.class)
+	public void testGetConnectionWithUserName() throws Exception {
+		dataSource.getConnection("user", "password");
+	}
+
+	@Test
+	public void testGetConnection() throws Exception {
+
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(2, dataSource.availableSize());
+
+		Connection c1 = dataSource.getConnection();
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(1, dataSource.availableSize());
+
+		Connection c2 = dataSource.getConnection();
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(0, dataSource.availableSize());
+
+		Connection c3 = dataSource.getConnection();
+		assertEquals(3, dataSource.poolSize());
+		assertEquals(0, dataSource.availableSize());
+
+		c1.close();
+		assertEquals(3, dataSource.poolSize());
+		assertEquals(1, dataSource.availableSize());
+
+		c2.close();
+		assertEquals(3, dataSource.poolSize());
+		assertEquals(2, dataSource.availableSize());
+
+		c3.close();
+		assertEquals(3, dataSource.poolSize());
+		assertEquals(3, dataSource.availableSize());
+	}
+
+	@Test
+	public void testGetConnection_BeforeScopeEnd() throws Exception {
+
+		assertEquals(2, dataSource.poolSize());
+		assertEquals(2, dataSource.availableSize());
+
+		dataSource.shutdown();
+
+		assertEquals(0, dataSource.poolSize());
+		assertEquals(0, dataSource.availableSize());
+	}
+
+	@Test
+	public void testGetConnection_Concurrent() {
+
+		PoolTask[] tasks = new PoolTask[2];
+		for (int i = 0; i < tasks.length; i++) {
+			tasks[i] = new PoolTask();
+		}
+
+		ExecutorService executor = Executors.newFixedThreadPool(tasks.length);
+
+		for (int j = 0; j < 100; j++) {
+			for (int i = 0; i < tasks.length; i++) {
+				executor.submit(tasks[i]);
+			}
+		}
+
+		// check for completion or deadlock
+		executor.shutdown();
+		try {
+			// normally this completes in less than 2 seconds. If it takes 30
+			// then it failed.
+			boolean didFinish = executor.awaitTermination(30, TimeUnit.SECONDS);
+			if (!didFinish) {
+				fail("Connection pool either deadlocked or contended over the lock too long.");
+			}
+		} catch (InterruptedException e) {
+			throw new RuntimeException(e);
+		}
+
+		for (int i = 0; i < tasks.length; i++) {
+			assertEquals(100, tasks[i].i.get());
+		}
+	}
+
+	class PoolTask implements Runnable {
+
+		AtomicInteger i = new AtomicInteger();
+
+		@Override
+		public void run() {
+
+			i.incrementAndGet();
+			try {
+				Connection c = dataSource.getConnection();
+				try {
+
+					Statement st = c.createStatement();
+					try {
+
+						ResultSet rs = st.executeQuery("SELECT ARTIST_ID FROM ARTIST");
+
+						try {
+							rs.next();
+
+						} finally {
+							rs.close();
+						}
+					} finally {
+						st.close();
+					}
+
+				} finally {
+					c.close();
+				}
+
+			} catch (SQLException e) {
+				e.printStackTrace();
+			}
+		}
+
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/517df07b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
new file mode 100644
index 0000000..25b7e7e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
@@ -0,0 +1,196 @@
+/*****************************************************************
+ *   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.datasource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.datasource.PoolAwareConnection;
+import org.apache.cayenne.datasource.PoolingDataSource;
+import org.apache.cayenne.datasource.PoolingDataSourceParameters;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class PoolingDataSourceTest {
+
+	private DataSource nonPooling;
+	private PoolingDataSourceParameters params;
+
+	@Before
+	public void before() throws SQLException {
+		nonPooling = mock(DataSource.class);
+		when(nonPooling.getConnection()).thenAnswer(new Answer<Connection>() {
+			@Override
+			public Connection answer(InvocationOnMock invocation) throws Throwable {
+				return mock(Connection.class);
+			}
+		});
+		params = new PoolingDataSourceParameters();
+	}
+
+	@Test
+	public void testManagePool_High() throws SQLException {
+
+		int max = 5;
+
+		params.setMinConnections(1);
+		params.setMaxConnections(max);
+		PoolingDataSource ds = new PoolingDataSource(nonPooling, params);
+
+		// opening and closing 'max' connections should fill the pool to the
+		// top...
+		Connection[] open = new Connection[max];
+		for (int i = 0; i < max; i++) {
+			open[i] = ds.getConnection();
+		}
+
+		for (Connection c : open) {
+			c.close();
+		}
+
+		// now we can start calling 'managePool', and it would close connections
+		// one at a time until we reach a threshold on idle
+		assertEquals(max, ds.poolSize());
+		ds.managePool();
+		assertEquals(max - 1, ds.poolSize());
+		ds.managePool();
+		assertEquals(max - 2, ds.poolSize());
+
+		// pool equilibrium was reached. subsequent calls should not open or
+		// close connections
+		ds.managePool();
+		assertEquals(max - 2, ds.poolSize());
+		ds.managePool();
+		assertEquals(max - 2, ds.poolSize());
+	}
+
+	@Test
+	public void testManagePool_Low() throws SQLException {
+
+		int min = 2;
+
+		params.setMinConnections(min);
+		params.setMaxConnections(min + 5);
+		PoolingDataSource ds = new PoolingDataSource(nonPooling, params);
+
+		// we start with a min number of connections
+		assertEquals(min, ds.poolSize());
+
+		// now lets evict a bunch of connections before we can start growing the
+		// pool again
+		for (int i = 0; i < min; i++) {
+			ds.retire(ds.uncheckNonBlocking(false));
+		}
+
+		// now we can start calling 'managePool', and it would open connections
+		// one at a time
+		assertEquals(0, ds.poolSize());
+		ds.managePool();
+		assertEquals(1, ds.poolSize());
+		ds.managePool();
+		assertEquals(2, ds.poolSize());
+
+		// pool equilibrium was reached. subsequent calls should not open or
+		// close connections
+		ds.managePool();
+		assertEquals(2, ds.poolSize());
+		ds.managePool();
+		assertEquals(2, ds.poolSize());
+	}
+
+	@Test
+	public void testValidateUnchecked() {
+
+		final PoolAwareConnection[] connections = validConnections(4);
+
+		params.setMinConnections(4);
+		params.setMaxConnections(10);
+
+		PoolingDataSource ds = new PoolingDataSource(nonPooling, params) {
+
+			int i;
+
+			@Override
+			PoolAwareConnection createWrapped() throws SQLException {
+				return connections[i++];
+			}
+		};
+
+		// now that the pool is created, invalidate a few leading connections
+		when(connections[0].validate()).thenReturn(false);
+		when(connections[1].validate()).thenReturn(false);
+
+		Connection faceHeadConnection = mock(Connection.class);
+		PoolAwareConnection fakeHead = mock(PoolAwareConnection.class);
+		when(fakeHead.getConnection()).thenReturn(faceHeadConnection);
+
+		assertSame(connections[2], ds.validateUnchecked(fakeHead));
+	}
+
+	@Test
+	public void testGetConnection_UpperCap() throws SQLException {
+		int max = 5;
+		params.setMaxConnections(max);
+		params.setMaxQueueWaitTime(1000);
+		PoolingDataSource ds = new PoolingDataSource(nonPooling, params);
+
+		Connection[] unchecked = new Connection[max];
+
+		for (int i = 0; i < max; i++) {
+			unchecked[i] = ds.getConnection();
+		}
+
+		try {
+			ds.getConnection();
+			fail("Pool overflow not checked");
+		} catch (SQLException e) {
+			// expected ... all connections are taken
+		}
+
+		// return one connection ... it should become immediately available
+		unchecked[0].close();
+
+		Connection c = ds.getConnection();
+		assertNotNull(c);
+	}
+
+	PoolAwareConnection[] validConnections(int size) {
+		PoolAwareConnection[] connections = new PoolAwareConnection[size];
+		for (int i = 0; i < size; i++) {
+			Connection c = mock(Connection.class);
+
+			connections[i] = mock(PoolAwareConnection.class);
+			when(connections[i].getConnection()).thenReturn(c);
+			when(connections[i].validate()).thenReturn(true);
+		}
+		return connections;
+	}
+
+}