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/04/30 17:59:11 UTC
[1/2] cayenne git commit: CAY-2008 Connection pool refactoring and
validation query support in Cayenne DataSource
Repository: cayenne
Updated Branches:
refs/heads/master 5e037fc73 -> 8b54c052d
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIT.java
index dbf985e..88bf277 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIT.java
@@ -19,6 +19,29 @@
package org.apache.cayenne.access;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
@@ -28,7 +51,6 @@ import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.ResultBatchIterator;
import org.apache.cayenne.ResultIterator;
import org.apache.cayenne.ResultIteratorCallback;
-import org.apache.cayenne.conn.PoolManager;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
@@ -59,575 +81,552 @@ import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Before;
import org.junit.Test;
-import java.math.BigDecimal;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
public class DataContextIT extends ServerCase {
- @Inject
- protected DataContext context;
-
- @Inject
- protected DBHelper dbHelper;
-
- @Inject
- protected UnitDbAdapter accessStackAdapter;
-
- @Inject
- protected DataChannelInterceptor queryInterceptor;
-
- @Inject
- protected ServerCaseDataSourceFactory dataSourceFactory;
-
- protected TableHelper tArtist;
- protected TableHelper tExhibit;
- protected TableHelper tGallery;
- protected TableHelper tPainting;
-
- @Before
- public void setUp() throws Exception {
- tArtist = new TableHelper(dbHelper, "ARTIST");
- tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
-
- tExhibit = new TableHelper(dbHelper, "EXHIBIT");
- tExhibit.setColumns("EXHIBIT_ID", "GALLERY_ID", "OPENING_DATE", "CLOSING_DATE");
-
- tGallery = new TableHelper(dbHelper, "GALLERY");
- tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
-
- tPainting = new TableHelper(dbHelper, "PAINTING");
- tPainting.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "ESTIMATED_PRICE");
- }
-
- protected void createSingleArtistDataSet() throws Exception {
- tArtist.insert(33001, "artist1");
- }
-
- protected void createFiveArtistDataSet_MixedCaseName() throws Exception {
- tArtist.insert(33001, "artist1");
- tArtist.insert(33002, "Artist3");
- tArtist.insert(33003, "aRtist5");
- tArtist.insert(33004, "arTist2");
- tArtist.insert(33005, "artISt4");
- }
-
- protected void createGalleriesAndExhibitsDataSet() throws Exception {
-
- tGallery.insert(33001, "gallery1");
- tGallery.insert(33002, "gallery2");
- tGallery.insert(33003, "gallery3");
- tGallery.insert(33004, "gallery4");
-
- Timestamp now = new Timestamp(System.currentTimeMillis());
-
- tExhibit.insert(1, 33001, now, now);
- tExhibit.insert(2, 33002, now, now);
- }
-
- protected void createArtistsDataSet() throws Exception {
- tArtist.insert(33001, "artist1");
- tArtist.insert(33002, "artist2");
- tArtist.insert(33003, "artist3");
- tArtist.insert(33004, "artist4");
- tArtist.insert(33005, "artist5");
- tArtist.insert(33006, "artist11");
- tArtist.insert(33007, "artist21");
- }
-
- protected void createLargeArtistsDataSet() throws Exception {
- for (int i = 1; i <= 20; i++) {
- tArtist.insert(i, "artist" + i);
- }
- }
-
- protected void createArtistsAndPaintingsDataSet() throws Exception {
- createArtistsDataSet();
-
- tPainting.insert(33001, "P_artist1", 33001, 1000);
- tPainting.insert(33002, "P_artist2", 33002, 2000);
- tPainting.insert(33003, "P_artist3", 33003, 3000);
- tPainting.insert(33004, "P_artist4", 33004, 4000);
- tPainting.insert(33005, "P_artist5", 33005, 5000);
- tPainting.insert(33006, "P_artist11", 33006, 11000);
- tPainting.insert(33007, "P_artist21", 33007, 21000);
- }
-
- @Test
- public void testCurrentSnapshot1() throws Exception {
- createSingleArtistDataSet();
-
- SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- Artist artist = (Artist) context.performQuery(query).get(0);
-
- DataRow snapshot = context.currentSnapshot(artist);
- assertEquals(artist.getArtistName(), snapshot.get("ARTIST_NAME"));
- assertEquals(artist.getDateOfBirth(), snapshot.get("DATE_OF_BIRTH"));
- }
-
- @Test
- public void testCurrentSnapshot2() throws Exception {
- createSingleArtistDataSet();
-
- // test null values
- SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- Artist artist = (Artist) context.performQuery(query).get(0);
-
- artist.setArtistName(null);
- artist.setDateOfBirth(null);
-
- DataRow snapshot = context.currentSnapshot(artist);
- assertTrue(snapshot.containsKey("ARTIST_NAME"));
- assertNull(snapshot.get("ARTIST_NAME"));
-
- assertTrue(snapshot.containsKey("DATE_OF_BIRTH"));
- assertNull(snapshot.get("DATE_OF_BIRTH"));
- }
-
- @Test
- public void testCurrentSnapshot3() throws Exception {
- createSingleArtistDataSet();
-
- // test null values
- SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- Artist artist = (Artist) context.performQuery(query).get(0);
-
- // test FK relationship snapshotting
- Painting p1 = new Painting();
- context.registerNewObject(p1);
- p1.setToArtist(artist);
-
- DataRow s1 = context.currentSnapshot(p1);
- Map<String, Object> idMap = artist.getObjectId().getIdSnapshot();
- assertEquals(idMap.get("ARTIST_ID"), s1.get("ARTIST_ID"));
- }
-
- /**
- * Testing snapshot with to-one fault. This was a bug CAY-96.
- */
- @Test
- public void testCurrentSnapshotWithToOneFault() throws Exception {
-
- createGalleriesAndExhibitsDataSet();
-
- // Exhibit with Gallery as Fault must still include Gallery
- // Artist and Exhibit (Exhibit has unresolved to-one to gallery as in
- // the
- // CAY-96 bug report)
-
- ObjectId eId = new ObjectId("Exhibit", Exhibit.EXHIBIT_ID_PK_COLUMN, 2);
- Exhibit e = (Exhibit) context.performQuery(new ObjectIdQuery(eId)).get(0);
-
- assertTrue(e.readPropertyDirectly(Exhibit.TO_GALLERY_PROPERTY) instanceof Fault);
-
- DataRow snapshot = context.currentSnapshot(e);
-
- // assert that after taking a snapshot, we have FK in, but the
- // relationship
- // is still a Fault
- assertTrue(e.readPropertyDirectly(Exhibit.TO_GALLERY_PROPERTY) instanceof Fault);
- assertEquals(new Integer(33002), snapshot.get("GALLERY_ID"));
- }
-
- /**
- * Tests how CHAR field is handled during fetch. Some databases (Oracle...)
- * would pad a CHAR column with extra spaces, returned to the client.
- * Cayenne should trim it.
- */
- @Test
- public void testCharFetch() throws Exception {
- createSingleArtistDataSet();
-
- SelectQuery query = new SelectQuery(Artist.class);
- Artist a = (Artist) context.performQuery(query).get(0);
- assertEquals(a.getArtistName().trim(), a.getArtistName());
- }
-
- /**
- * Tests how CHAR field is handled during fetch in the WHERE clause. Some
- * databases (Oracle...) would pad a CHAR column with extra spaces, returned
- * to the client. Cayenne should trim it.
- */
- @Test
- public void testCharInQualifier() throws Exception {
- createArtistsDataSet();
-
- Expression e = ExpressionFactory.matchExp("artistName", "artist1");
- SelectQuery q = new SelectQuery(Artist.class, e);
- List<Artist> artists = context.performQuery(q);
- assertEquals(1, artists.size());
- }
-
- /**
- * Test fetching query with multiple relationship paths between the same 2
- * entities used in qualifier.
- */
- @Test
- public void testMultiObjRelFetch() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- SelectQuery q = new SelectQuery(Painting.class);
- q.andQualifier(ExpressionFactory.matchExp("toArtist.artistName", "artist2"));
- q.orQualifier(ExpressionFactory.matchExp("toArtist.artistName", "artist4"));
- List<Painting> results = context.performQuery(q);
-
- assertEquals(2, results.size());
- }
-
- /**
- * Test fetching query with multiple relationship paths between the same 2
- * entities used in qualifier.
- */
- @Test
- public void testMultiDbRelFetch() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- SelectQuery q = new SelectQuery("Painting");
- q.andQualifier(ExpressionFactory.matchDbExp("toArtist.ARTIST_NAME", "artist2"));
- q.orQualifier(ExpressionFactory.matchDbExp("toArtist.ARTIST_NAME", "artist4"));
- List<?> results = context.performQuery(q);
-
- assertEquals(2, results.size());
- }
-
- @Test
- public void testSelectDate() throws Exception {
- createGalleriesAndExhibitsDataSet();
-
- List<Exhibit> objects = context.performQuery(new SelectQuery(Exhibit.class));
- assertFalse(objects.isEmpty());
-
- Exhibit e1 = objects.get(0);
- assertEquals(java.util.Date.class, e1.getClosingDate().getClass());
- }
-
- @Test
- public void testCaseInsensitiveOrdering() throws Exception {
- if (!accessStackAdapter.supportsCaseInsensitiveOrder()) {
- return;
- }
-
- createFiveArtistDataSet_MixedCaseName();
-
- // case insensitive ordering appends extra columns
- // to the query when query is using DISTINCT...
- // verify that the result is not messed up
-
- SelectQuery query = new SelectQuery(Artist.class);
- Ordering ordering = new Ordering(Artist.ARTIST_NAME_PROPERTY, SortOrder.ASCENDING_INSENSITIVE);
- query.addOrdering(ordering);
- query.setDistinct(true);
-
- List<Artist> objects = context.performQuery(query);
- assertEquals(5, objects.size());
-
- Artist artist = objects.get(0);
- DataRow snapshot = context.getObjectStore().getSnapshot(artist.getObjectId());
- assertEquals(3, snapshot.size());
-
- // assert the ordering
- assertEquals("artist1", objects.get(0).getArtistName());
- assertEquals("arTist2", objects.get(1).getArtistName());
- assertEquals("Artist3", objects.get(2).getArtistName());
- assertEquals("artISt4", objects.get(3).getArtistName());
- assertEquals("aRtist5", objects.get(4).getArtistName());
- }
-
- @Test
- public void testSelect_DataRows() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- SelectQuery<DataRow> query = SelectQuery.dataRowQuery(Artist.class, null);
- List<DataRow> objects = context.select(query);
-
- assertNotNull(objects);
- assertEquals(7, objects.size());
- assertTrue("DataRow expected, got " + objects.get(0).getClass(), objects.get(0) instanceof DataRow);
- }
-
- @Test
- public void testPerformSelectQuery1() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- SelectQuery query = new SelectQuery(Artist.class);
- List<?> objects = context.performQuery(query);
-
- assertNotNull(objects);
- assertEquals(7, objects.size());
- assertTrue("Artist expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Artist);
- }
-
- @Test
- public void testPerformSelectQuery2() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- // do a query with complex qualifier
- List<Expression> expressions = new ArrayList<Expression>();
- expressions.add(ExpressionFactory.matchExp("artistName", "artist3"));
- expressions.add(ExpressionFactory.matchExp("artistName", "artist5"));
- expressions.add(ExpressionFactory.matchExp("artistName", "artist21"));
-
- SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.joinExp(Expression.OR, expressions));
-
- List<?> objects = context.performQuery(query);
-
- assertNotNull(objects);
- assertEquals(3, objects.size());
- assertTrue("Artist expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Artist);
- }
-
- @Test
- public void testPerformQuery_Routing() {
- Query query = mock(Query.class);
- QueryMetadata md = mock(QueryMetadata.class);
- when(query.getMetaData(any(EntityResolver.class))).thenReturn(md);
- context.performGenericQuery(query);
- verify(query).route(any(QueryRouter.class), eq(context.getEntityResolver()), (Query) isNull());
- }
-
- @Test
- public void testPerformNonSelectingQuery() throws Exception {
-
- createSingleArtistDataSet();
-
- SelectQuery select = new SelectQuery(Painting.class, Expression.fromString("db:PAINTING_ID = 1"));
-
- assertEquals(0, context.performQuery(select).size());
-
- SQLTemplate query = new SQLTemplate(Painting.class,
- "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
- + "VALUES (1, 'PX', 33001, 1)");
- context.performNonSelectingQuery(query);
- assertEquals(1, context.performQuery(select).size());
- }
-
- @Test
- public void testPerformNonSelectingQueryCounts1() throws Exception {
- createArtistsDataSet();
-
- SQLTemplate query = new SQLTemplate(Painting.class,
- "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
- + "VALUES ($pid, '$pt', $aid, $price)");
-
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("pid", new Integer(1));
- map.put("pt", "P1");
- map.put("aid", new Integer(33002));
- map.put("price", new Double(1.1));
-
- // single batch of parameters
- query.setParameters(map);
-
- int[] counts = context.performNonSelectingQuery(query);
- assertNotNull(counts);
- assertEquals(1, counts.length);
- assertEquals(1, counts[0]);
- }
-
- @Test
- public void testPerformNonSelectingQueryCounts2() throws Exception {
-
- createArtistsDataSet();
-
- SQLTemplate query = new SQLTemplate(Painting.class,
- "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
- + "VALUES ($pid, '$pt', $aid, #bind($price 'DECIMAL' 2))");
-
- Map<String, Object>[] maps = new Map[3];
- for (int i = 0; i < maps.length; i++) {
- maps[i] = new HashMap<String, Object>();
- maps[i].put("pid", new Integer(1 + i));
- maps[i].put("pt", "P-" + i);
- maps[i].put("aid", new Integer(33002));
- maps[i].put("price", new BigDecimal("1." + i));
- }
-
- // single batch of parameters
- query.setParameters(maps);
-
- int[] counts = context.performNonSelectingQuery(query);
- assertNotNull(counts);
- assertEquals(maps.length, counts.length);
- for (int i = 0; i < maps.length; i++) {
- assertEquals(1, counts[i]);
- }
-
- SQLTemplate delete = new SQLTemplate(Painting.class, "delete from PAINTING");
- counts = context.performNonSelectingQuery(delete);
- assertNotNull(counts);
- assertEquals(1, counts.length);
- assertEquals(3, counts[0]);
- }
-
- @Test
- public void testPerformPaginatedQuery() throws Exception {
- createArtistsDataSet();
-
- SelectQuery<Artist> query = SelectQuery.query(Artist.class);
- query.setPageSize(5);
- List<Artist> objects = context.select(query);
- assertNotNull(objects);
- assertTrue(objects instanceof IncrementalFaultList<?>);
- assertTrue(((IncrementalFaultList<Artist>) objects).elements.get(0) instanceof Long);
- assertTrue(((IncrementalFaultList<Artist>) objects).elements.get(6) instanceof Long);
-
- assertTrue(objects.get(0) instanceof Artist);
- }
-
- @Test
- public void testPerformPaginatedQuery1() throws Exception {
- createArtistsDataSet();
-
- EJBQLQuery query = new EJBQLQuery("select a FROM Artist a");
- query.setPageSize(5);
- List<?> objects = context.performQuery(query);
- assertNotNull(objects);
- assertTrue(objects instanceof IncrementalFaultList<?>);
- assertTrue(((IncrementalFaultList<?>) objects).elements.get(0) instanceof Long);
- assertTrue(((IncrementalFaultList<?>) objects).elements.get(6) instanceof Long);
-
- assertTrue(objects.get(0) instanceof Artist);
- }
-
- @Test
- public void testPerformPaginatedQueryBigPage() throws Exception {
- createArtistsDataSet();
-
- SelectQuery query = new SelectQuery(Artist.class);
- query.setPageSize(5);
- final List<?> objects = context.performQuery(query);
- assertNotNull(objects);
- assertTrue(objects instanceof IncrementalFaultList<?>);
-
- queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() {
-
- public void execute() {
- assertEquals(7, objects.size());
- }
- });
- }
-
- @Test
- public void testPerformDataRowQuery() throws Exception {
-
- createArtistsDataSet();
-
- SelectQuery query = new SelectQuery(Artist.class);
- query.setFetchingDataRows(true);
- List<?> objects = context.performQuery(query);
-
- assertNotNull(objects);
- assertEquals(7, objects.size());
- assertTrue("Map expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Map<?, ?>);
- }
-
- @Test
- public void testCommitChangesRO1() throws Exception {
-
- ROArtist a1 = (ROArtist) context.newObject("ROArtist");
- a1.writePropertyDirectly("artistName", "abc");
- a1.setPersistenceState(PersistenceState.MODIFIED);
-
- try {
- context.commitChanges();
- fail("Inserting a 'read-only' object must fail.");
- } catch (Exception ex) {
- // exception is expected,
- // must blow on saving new "read-only" object.
- }
- }
-
- @Test
- public void testCommitChangesRO2() throws Exception {
- createArtistsDataSet();
-
- SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
- a1.writeProperty(ROArtist.ARTIST_NAME_PROPERTY, "abc");
-
- try {
- context.commitChanges();
- fail("Updating a 'read-only' object must fail.");
- } catch (Exception ex) {
- // exception is expected,
- // must blow on saving new "read-only" object.
- }
- }
-
- @Test
- public void testCommitChangesRO3() throws Exception {
-
- createArtistsDataSet();
-
- SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
- context.deleteObjects(a1);
-
- try {
- context.commitChanges();
- fail("Deleting a 'read-only' object must fail.");
- } catch (Exception ex) {
- // exception is expected,
- // must blow on saving new "read-only" object.
- }
- }
-
- @Test
- public void testCommitChangesRO4() throws Exception {
- createArtistsDataSet();
-
- SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
- "artist1"));
- ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
-
- Painting painting = context.newObject(Painting.class);
- painting.setPaintingTitle("paint");
- a1.addToPaintingArray(painting);
-
- assertEquals(PersistenceState.MODIFIED, a1.getPersistenceState());
- try {
- context.commitChanges();
- } catch (Exception ex) {
- fail("Updating 'read-only' object's to-many must succeed, instead an exception was thrown: " + ex);
- }
-
- assertEquals(PersistenceState.COMMITTED, a1.getPersistenceState());
- }
-
- @Test
+ @Inject
+ protected DataContext context;
+
+ @Inject
+ protected DBHelper dbHelper;
+
+ @Inject
+ protected UnitDbAdapter accessStackAdapter;
+
+ @Inject
+ protected DataChannelInterceptor queryInterceptor;
+
+ @Inject
+ protected ServerCaseDataSourceFactory dataSourceFactory;
+
+ protected TableHelper tArtist;
+ protected TableHelper tExhibit;
+ protected TableHelper tGallery;
+ protected TableHelper tPainting;
+
+ @Before
+ public void setUp() throws Exception {
+ tArtist = new TableHelper(dbHelper, "ARTIST");
+ tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
+
+ tExhibit = new TableHelper(dbHelper, "EXHIBIT");
+ tExhibit.setColumns("EXHIBIT_ID", "GALLERY_ID", "OPENING_DATE", "CLOSING_DATE");
+
+ tGallery = new TableHelper(dbHelper, "GALLERY");
+ tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
+
+ tPainting = new TableHelper(dbHelper, "PAINTING");
+ tPainting.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "ESTIMATED_PRICE");
+ }
+
+ protected void createSingleArtistDataSet() throws Exception {
+ tArtist.insert(33001, "artist1");
+ }
+
+ protected void createFiveArtistDataSet_MixedCaseName() throws Exception {
+ tArtist.insert(33001, "artist1");
+ tArtist.insert(33002, "Artist3");
+ tArtist.insert(33003, "aRtist5");
+ tArtist.insert(33004, "arTist2");
+ tArtist.insert(33005, "artISt4");
+ }
+
+ protected void createGalleriesAndExhibitsDataSet() throws Exception {
+
+ tGallery.insert(33001, "gallery1");
+ tGallery.insert(33002, "gallery2");
+ tGallery.insert(33003, "gallery3");
+ tGallery.insert(33004, "gallery4");
+
+ Timestamp now = new Timestamp(System.currentTimeMillis());
+
+ tExhibit.insert(1, 33001, now, now);
+ tExhibit.insert(2, 33002, now, now);
+ }
+
+ protected void createArtistsDataSet() throws Exception {
+ tArtist.insert(33001, "artist1");
+ tArtist.insert(33002, "artist2");
+ tArtist.insert(33003, "artist3");
+ tArtist.insert(33004, "artist4");
+ tArtist.insert(33005, "artist5");
+ tArtist.insert(33006, "artist11");
+ tArtist.insert(33007, "artist21");
+ }
+
+ protected void createLargeArtistsDataSet() throws Exception {
+ for (int i = 1; i <= 20; i++) {
+ tArtist.insert(i, "artist" + i);
+ }
+ }
+
+ protected void createArtistsAndPaintingsDataSet() throws Exception {
+ createArtistsDataSet();
+
+ tPainting.insert(33001, "P_artist1", 33001, 1000);
+ tPainting.insert(33002, "P_artist2", 33002, 2000);
+ tPainting.insert(33003, "P_artist3", 33003, 3000);
+ tPainting.insert(33004, "P_artist4", 33004, 4000);
+ tPainting.insert(33005, "P_artist5", 33005, 5000);
+ tPainting.insert(33006, "P_artist11", 33006, 11000);
+ tPainting.insert(33007, "P_artist21", 33007, 21000);
+ }
+
+ @Test
+ public void testCurrentSnapshot1() throws Exception {
+ createSingleArtistDataSet();
+
+ SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ Artist artist = (Artist) context.performQuery(query).get(0);
+
+ DataRow snapshot = context.currentSnapshot(artist);
+ assertEquals(artist.getArtistName(), snapshot.get("ARTIST_NAME"));
+ assertEquals(artist.getDateOfBirth(), snapshot.get("DATE_OF_BIRTH"));
+ }
+
+ @Test
+ public void testCurrentSnapshot2() throws Exception {
+ createSingleArtistDataSet();
+
+ // test null values
+ SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ Artist artist = (Artist) context.performQuery(query).get(0);
+
+ artist.setArtistName(null);
+ artist.setDateOfBirth(null);
+
+ DataRow snapshot = context.currentSnapshot(artist);
+ assertTrue(snapshot.containsKey("ARTIST_NAME"));
+ assertNull(snapshot.get("ARTIST_NAME"));
+
+ assertTrue(snapshot.containsKey("DATE_OF_BIRTH"));
+ assertNull(snapshot.get("DATE_OF_BIRTH"));
+ }
+
+ @Test
+ public void testCurrentSnapshot3() throws Exception {
+ createSingleArtistDataSet();
+
+ // test null values
+ SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ Artist artist = (Artist) context.performQuery(query).get(0);
+
+ // test FK relationship snapshotting
+ Painting p1 = new Painting();
+ context.registerNewObject(p1);
+ p1.setToArtist(artist);
+
+ DataRow s1 = context.currentSnapshot(p1);
+ Map<String, Object> idMap = artist.getObjectId().getIdSnapshot();
+ assertEquals(idMap.get("ARTIST_ID"), s1.get("ARTIST_ID"));
+ }
+
+ /**
+ * Testing snapshot with to-one fault. This was a bug CAY-96.
+ */
+ @Test
+ public void testCurrentSnapshotWithToOneFault() throws Exception {
+
+ createGalleriesAndExhibitsDataSet();
+
+ // Exhibit with Gallery as Fault must still include Gallery
+ // Artist and Exhibit (Exhibit has unresolved to-one to gallery as in
+ // the
+ // CAY-96 bug report)
+
+ ObjectId eId = new ObjectId("Exhibit", Exhibit.EXHIBIT_ID_PK_COLUMN, 2);
+ Exhibit e = (Exhibit) context.performQuery(new ObjectIdQuery(eId)).get(0);
+
+ assertTrue(e.readPropertyDirectly(Exhibit.TO_GALLERY_PROPERTY) instanceof Fault);
+
+ DataRow snapshot = context.currentSnapshot(e);
+
+ // assert that after taking a snapshot, we have FK in, but the
+ // relationship
+ // is still a Fault
+ assertTrue(e.readPropertyDirectly(Exhibit.TO_GALLERY_PROPERTY) instanceof Fault);
+ assertEquals(new Integer(33002), snapshot.get("GALLERY_ID"));
+ }
+
+ /**
+ * Tests how CHAR field is handled during fetch. Some databases (Oracle...)
+ * would pad a CHAR column with extra spaces, returned to the client.
+ * Cayenne should trim it.
+ */
+ @Test
+ public void testCharFetch() throws Exception {
+ createSingleArtistDataSet();
+
+ SelectQuery query = new SelectQuery(Artist.class);
+ Artist a = (Artist) context.performQuery(query).get(0);
+ assertEquals(a.getArtistName().trim(), a.getArtistName());
+ }
+
+ /**
+ * Tests how CHAR field is handled during fetch in the WHERE clause. Some
+ * databases (Oracle...) would pad a CHAR column with extra spaces, returned
+ * to the client. Cayenne should trim it.
+ */
+ @Test
+ public void testCharInQualifier() throws Exception {
+ createArtistsDataSet();
+
+ Expression e = ExpressionFactory.matchExp("artistName", "artist1");
+ SelectQuery q = new SelectQuery(Artist.class, e);
+ List<Artist> artists = context.performQuery(q);
+ assertEquals(1, artists.size());
+ }
+
+ /**
+ * Test fetching query with multiple relationship paths between the same 2
+ * entities used in qualifier.
+ */
+ @Test
+ public void testMultiObjRelFetch() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ SelectQuery q = new SelectQuery(Painting.class);
+ q.andQualifier(ExpressionFactory.matchExp("toArtist.artistName", "artist2"));
+ q.orQualifier(ExpressionFactory.matchExp("toArtist.artistName", "artist4"));
+ List<Painting> results = context.performQuery(q);
+
+ assertEquals(2, results.size());
+ }
+
+ /**
+ * Test fetching query with multiple relationship paths between the same 2
+ * entities used in qualifier.
+ */
+ @Test
+ public void testMultiDbRelFetch() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ SelectQuery q = new SelectQuery("Painting");
+ q.andQualifier(ExpressionFactory.matchDbExp("toArtist.ARTIST_NAME", "artist2"));
+ q.orQualifier(ExpressionFactory.matchDbExp("toArtist.ARTIST_NAME", "artist4"));
+ List<?> results = context.performQuery(q);
+
+ assertEquals(2, results.size());
+ }
+
+ @Test
+ public void testSelectDate() throws Exception {
+ createGalleriesAndExhibitsDataSet();
+
+ List<Exhibit> objects = context.performQuery(new SelectQuery(Exhibit.class));
+ assertFalse(objects.isEmpty());
+
+ Exhibit e1 = objects.get(0);
+ assertEquals(java.util.Date.class, e1.getClosingDate().getClass());
+ }
+
+ @Test
+ public void testCaseInsensitiveOrdering() throws Exception {
+ if (!accessStackAdapter.supportsCaseInsensitiveOrder()) {
+ return;
+ }
+
+ createFiveArtistDataSet_MixedCaseName();
+
+ // case insensitive ordering appends extra columns
+ // to the query when query is using DISTINCT...
+ // verify that the result is not messed up
+
+ SelectQuery query = new SelectQuery(Artist.class);
+ Ordering ordering = new Ordering(Artist.ARTIST_NAME_PROPERTY, SortOrder.ASCENDING_INSENSITIVE);
+ query.addOrdering(ordering);
+ query.setDistinct(true);
+
+ List<Artist> objects = context.performQuery(query);
+ assertEquals(5, objects.size());
+
+ Artist artist = objects.get(0);
+ DataRow snapshot = context.getObjectStore().getSnapshot(artist.getObjectId());
+ assertEquals(3, snapshot.size());
+
+ // assert the ordering
+ assertEquals("artist1", objects.get(0).getArtistName());
+ assertEquals("arTist2", objects.get(1).getArtistName());
+ assertEquals("Artist3", objects.get(2).getArtistName());
+ assertEquals("artISt4", objects.get(3).getArtistName());
+ assertEquals("aRtist5", objects.get(4).getArtistName());
+ }
+
+ @Test
+ public void testSelect_DataRows() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ SelectQuery<DataRow> query = SelectQuery.dataRowQuery(Artist.class, null);
+ List<DataRow> objects = context.select(query);
+
+ assertNotNull(objects);
+ assertEquals(7, objects.size());
+ assertTrue("DataRow expected, got " + objects.get(0).getClass(), objects.get(0) instanceof DataRow);
+ }
+
+ @Test
+ public void testPerformSelectQuery1() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ SelectQuery query = new SelectQuery(Artist.class);
+ List<?> objects = context.performQuery(query);
+
+ assertNotNull(objects);
+ assertEquals(7, objects.size());
+ assertTrue("Artist expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Artist);
+ }
+
+ @Test
+ public void testPerformSelectQuery2() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ // do a query with complex qualifier
+ List<Expression> expressions = new ArrayList<Expression>();
+ expressions.add(ExpressionFactory.matchExp("artistName", "artist3"));
+ expressions.add(ExpressionFactory.matchExp("artistName", "artist5"));
+ expressions.add(ExpressionFactory.matchExp("artistName", "artist21"));
+
+ SelectQuery query = new SelectQuery(Artist.class, ExpressionFactory.joinExp(Expression.OR, expressions));
+
+ List<?> objects = context.performQuery(query);
+
+ assertNotNull(objects);
+ assertEquals(3, objects.size());
+ assertTrue("Artist expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Artist);
+ }
+
+ @Test
+ public void testPerformQuery_Routing() {
+ Query query = mock(Query.class);
+ QueryMetadata md = mock(QueryMetadata.class);
+ when(query.getMetaData(any(EntityResolver.class))).thenReturn(md);
+ context.performGenericQuery(query);
+ verify(query).route(any(QueryRouter.class), eq(context.getEntityResolver()), (Query) isNull());
+ }
+
+ @Test
+ public void testPerformNonSelectingQuery() throws Exception {
+
+ createSingleArtistDataSet();
+
+ SelectQuery select = new SelectQuery(Painting.class, Expression.fromString("db:PAINTING_ID = 1"));
+
+ assertEquals(0, context.performQuery(select).size());
+
+ SQLTemplate query = new SQLTemplate(Painting.class,
+ "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
+ + "VALUES (1, 'PX', 33001, 1)");
+ context.performNonSelectingQuery(query);
+ assertEquals(1, context.performQuery(select).size());
+ }
+
+ @Test
+ public void testPerformNonSelectingQueryCounts1() throws Exception {
+ createArtistsDataSet();
+
+ SQLTemplate query = new SQLTemplate(Painting.class,
+ "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
+ + "VALUES ($pid, '$pt', $aid, $price)");
+
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("pid", new Integer(1));
+ map.put("pt", "P1");
+ map.put("aid", new Integer(33002));
+ map.put("price", new Double(1.1));
+
+ // single batch of parameters
+ query.setParameters(map);
+
+ int[] counts = context.performNonSelectingQuery(query);
+ assertNotNull(counts);
+ assertEquals(1, counts.length);
+ assertEquals(1, counts[0]);
+ }
+
+ @Test
+ public void testPerformNonSelectingQueryCounts2() throws Exception {
+
+ createArtistsDataSet();
+
+ SQLTemplate query = new SQLTemplate(Painting.class,
+ "INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE) "
+ + "VALUES ($pid, '$pt', $aid, #bind($price 'DECIMAL' 2))");
+
+ Map<String, Object>[] maps = new Map[3];
+ for (int i = 0; i < maps.length; i++) {
+ maps[i] = new HashMap<String, Object>();
+ maps[i].put("pid", new Integer(1 + i));
+ maps[i].put("pt", "P-" + i);
+ maps[i].put("aid", new Integer(33002));
+ maps[i].put("price", new BigDecimal("1." + i));
+ }
+
+ // single batch of parameters
+ query.setParameters(maps);
+
+ int[] counts = context.performNonSelectingQuery(query);
+ assertNotNull(counts);
+ assertEquals(maps.length, counts.length);
+ for (int i = 0; i < maps.length; i++) {
+ assertEquals(1, counts[i]);
+ }
+
+ SQLTemplate delete = new SQLTemplate(Painting.class, "delete from PAINTING");
+ counts = context.performNonSelectingQuery(delete);
+ assertNotNull(counts);
+ assertEquals(1, counts.length);
+ assertEquals(3, counts[0]);
+ }
+
+ @Test
+ public void testPerformPaginatedQuery() throws Exception {
+ createArtistsDataSet();
+
+ SelectQuery<Artist> query = SelectQuery.query(Artist.class);
+ query.setPageSize(5);
+ List<Artist> objects = context.select(query);
+ assertNotNull(objects);
+ assertTrue(objects instanceof IncrementalFaultList<?>);
+ assertTrue(((IncrementalFaultList<Artist>) objects).elements.get(0) instanceof Long);
+ assertTrue(((IncrementalFaultList<Artist>) objects).elements.get(6) instanceof Long);
+
+ assertTrue(objects.get(0) instanceof Artist);
+ }
+
+ @Test
+ public void testPerformPaginatedQuery1() throws Exception {
+ createArtistsDataSet();
+
+ EJBQLQuery query = new EJBQLQuery("select a FROM Artist a");
+ query.setPageSize(5);
+ List<?> objects = context.performQuery(query);
+ assertNotNull(objects);
+ assertTrue(objects instanceof IncrementalFaultList<?>);
+ assertTrue(((IncrementalFaultList<?>) objects).elements.get(0) instanceof Long);
+ assertTrue(((IncrementalFaultList<?>) objects).elements.get(6) instanceof Long);
+
+ assertTrue(objects.get(0) instanceof Artist);
+ }
+
+ @Test
+ public void testPerformPaginatedQueryBigPage() throws Exception {
+ createArtistsDataSet();
+
+ SelectQuery query = new SelectQuery(Artist.class);
+ query.setPageSize(5);
+ final List<?> objects = context.performQuery(query);
+ assertNotNull(objects);
+ assertTrue(objects instanceof IncrementalFaultList<?>);
+
+ queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() {
+
+ public void execute() {
+ assertEquals(7, objects.size());
+ }
+ });
+ }
+
+ @Test
+ public void testPerformDataRowQuery() throws Exception {
+
+ createArtistsDataSet();
+
+ SelectQuery query = new SelectQuery(Artist.class);
+ query.setFetchingDataRows(true);
+ List<?> objects = context.performQuery(query);
+
+ assertNotNull(objects);
+ assertEquals(7, objects.size());
+ assertTrue("Map expected, got " + objects.get(0).getClass(), objects.get(0) instanceof Map<?, ?>);
+ }
+
+ @Test
+ public void testCommitChangesRO1() throws Exception {
+
+ ROArtist a1 = (ROArtist) context.newObject("ROArtist");
+ a1.writePropertyDirectly("artistName", "abc");
+ a1.setPersistenceState(PersistenceState.MODIFIED);
+
+ try {
+ context.commitChanges();
+ fail("Inserting a 'read-only' object must fail.");
+ } catch (Exception ex) {
+ // exception is expected,
+ // must blow on saving new "read-only" object.
+ }
+ }
+
+ @Test
+ public void testCommitChangesRO2() throws Exception {
+ createArtistsDataSet();
+
+ SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
+ a1.writeProperty(ROArtist.ARTIST_NAME_PROPERTY, "abc");
+
+ try {
+ context.commitChanges();
+ fail("Updating a 'read-only' object must fail.");
+ } catch (Exception ex) {
+ // exception is expected,
+ // must blow on saving new "read-only" object.
+ }
+ }
+
+ @Test
+ public void testCommitChangesRO3() throws Exception {
+
+ createArtistsDataSet();
+
+ SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
+ context.deleteObjects(a1);
+
+ try {
+ context.commitChanges();
+ fail("Deleting a 'read-only' object must fail.");
+ } catch (Exception ex) {
+ // exception is expected,
+ // must blow on saving new "read-only" object.
+ }
+ }
+
+ @Test
+ public void testCommitChangesRO4() throws Exception {
+ createArtistsDataSet();
+
+ SelectQuery query = new SelectQuery(ROArtist.class, ExpressionFactory.matchExp(Artist.ARTIST_NAME_PROPERTY,
+ "artist1"));
+ ROArtist a1 = (ROArtist) context.performQuery(query).get(0);
+
+ Painting painting = context.newObject(Painting.class);
+ painting.setPaintingTitle("paint");
+ a1.addToPaintingArray(painting);
+
+ assertEquals(PersistenceState.MODIFIED, a1.getPersistenceState());
+ try {
+ context.commitChanges();
+ } catch (Exception ex) {
+ fail("Updating 'read-only' object's to-many must succeed, instead an exception was thrown: " + ex);
+ }
+
+ assertEquals(PersistenceState.COMMITTED, a1.getPersistenceState());
+ }
+
+ @Test
public void testIterate() throws Exception {
createArtistsDataSet();
SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
-
+
final int[] count = new int[1];
-
+
context.iterate(q1, new ResultIteratorCallback<Artist>() {
@Override
@@ -636,235 +635,225 @@ public class DataContextIT extends ServerCase {
count[0]++;
}
});
-
- assertEquals(7, count[0]);
+
+ assertEquals(7, count[0]);
+ }
+
+ @Test
+ public void testIterateDataRows() throws Exception {
+
+ createArtistsDataSet();
+
+ SelectQuery<DataRow> q1 = SelectQuery.dataRowQuery(Artist.class, null);
+ final int[] count = new int[1];
+
+ context.iterate(q1, new ResultIteratorCallback<DataRow>() {
+
+ @Override
+ public void next(DataRow object) {
+ assertNotNull(object.get("ARTIST_ID"));
+ count[0]++;
+ }
+ });
+
+ assertEquals(7, count[0]);
+ }
+
+ @Test
+ public void testIterator() throws Exception {
+
+ createArtistsDataSet();
+
+ SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+
+ ResultIterator<Artist> it = context.iterator(q1);
+ try {
+ int count = 0;
+
+ for (Artist a : it) {
+ count++;
+ }
+
+ assertEquals(7, count);
+ } finally {
+ it.close();
+ }
+ }
+
+ @Test
+ public void testBatchIterator() throws Exception {
+ createLargeArtistsDataSet();
+
+ SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+ ResultBatchIterator<Artist> it = context.batchIterator(q1, 5);
+
+ try {
+ int count = 0;
+
+ for (List<Artist> artistList : it) {
+ count++;
+ assertEquals(5, artistList.size());
+ }
+
+ assertEquals(4, count);
+ } finally {
+ it.close();
+ }
+ }
+
+ @Test
+ public void testPerformIteratedQuery1() throws Exception {
+
+ createArtistsDataSet();
+
+ SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+ ResultIterator<?> it = context.performIteratedQuery(q1);
+
+ try {
+ int count = 0;
+ while (it.hasNextRow()) {
+ it.nextRow();
+ count++;
+ }
+
+ assertEquals(7, count);
+ } finally {
+ it.close();
+ }
+ }
+
+ @Test
+ public void testPerformIteratedQuery2() throws Exception {
+ createArtistsAndPaintingsDataSet();
+
+ ResultIterator<?> it = context.performIteratedQuery(SelectQuery.query(Artist.class));
+
+ try {
+ while (it.hasNextRow()) {
+ DataRow row = (DataRow) it.nextRow();
+
+ // try instantiating an object and fetching its relationships
+ Artist artist = context.objectFromDataRow(Artist.class, row);
+ List<?> paintings = artist.getPaintingArray();
+ assertNotNull(paintings);
+ assertEquals("Expected one painting for artist: " + artist, 1, paintings.size());
+ }
+ } finally {
+ it.close();
+ }
+ }
+
+ /**
+ * Tests that hasChanges performs correctly when an object is "modified" and
+ * the property is simply set to the same value (an unreal modification)
+ */
+ @Test
+ public void testHasChangesPhantom() {
+
+ String artistName = "ArtistName";
+ Artist artist = (Artist) context.newObject("Artist");
+ artist.setArtistName(artistName);
+ context.commitChanges();
+
+ // Set again to *exactly* the same value
+ artist.setArtistName(artistName);
+
+ // note that since 1.2 the polciy is for hasChanges to return true for
+ // phantom
+ // modifications, as there is no way to detect some more subtle
+ // modifications like
+ // a change of the master related object, until we actually create the
+ // PKs
+ assertTrue(context.hasChanges());
+ }
+
+ /**
+ * Tests that hasChanges performs correctly when an object is "modified" and
+ * the property is simply set to the same value (an unreal modification)
+ */
+ @Test
+ public void testHasChangesRealModify() {
+ Artist artist = (Artist) context.newObject("Artist");
+ artist.setArtistName("ArtistName");
+ context.commitChanges();
+
+ artist.setArtistName("Something different");
+ assertTrue(context.hasChanges());
+ }
+
+ @Test
+ public void testInvalidateObjects_Vararg() throws Exception {
+
+ DataRow row = new DataRow(10);
+ row.put("ARTIST_ID", new Integer(1));
+ row.put("ARTIST_NAME", "ArtistXYZ");
+ row.put("DATE_OF_BIRTH", new Date());
+ DataObject object = context.objectFromDataRow(Artist.class, row);
+ ObjectId oid = object.getObjectId();
+
+ // insert object into the ObjectStore
+ context.getObjectStore().registerNode(oid, object);
+
+ assertSame(object, context.getObjectStore().getNode(oid));
+ assertNotNull(context.getObjectStore().getCachedSnapshot(oid));
+
+ context.invalidateObjects(object);
+
+ assertSame(oid, object.getObjectId());
+ assertNull(context.getObjectStore().getCachedSnapshot(oid));
+ assertSame(object, context.getObjectStore().getNode(oid));
+ }
+
+ @Test
+ public void testInvalidateObjects() throws Exception {
+
+ DataRow row = new DataRow(10);
+ row.put("ARTIST_ID", new Integer(1));
+ row.put("ARTIST_NAME", "ArtistXYZ");
+ row.put("DATE_OF_BIRTH", new Date());
+ DataObject object = context.objectFromDataRow(Artist.class, row);
+ ObjectId oid = object.getObjectId();
+
+ // insert object into the ObjectStore
+ context.getObjectStore().registerNode(oid, object);
+
+ assertSame(object, context.getObjectStore().getNode(oid));
+ assertNotNull(context.getObjectStore().getCachedSnapshot(oid));
+
+ context.invalidateObjects(Collections.singleton(object));
+
+ assertSame(oid, object.getObjectId());
+ assertNull(context.getObjectStore().getCachedSnapshot(oid));
+ assertSame(object, context.getObjectStore().getNode(oid));
+ }
+
+ @Test
+ public void testBeforeHollowDeleteShouldChangeStateToCommited() throws Exception {
+ createSingleArtistDataSet();
+
+ Artist hollow = Cayenne.objectForPK(context, Artist.class, 33001);
+ context.invalidateObjects(hollow);
+ assertEquals(PersistenceState.HOLLOW, hollow.getPersistenceState());
+
+ // testing this...
+ context.deleteObjects(hollow);
+ assertSame(hollow, context.getGraphManager().getNode(new ObjectId("Artist", "ARTIST_ID", 33001)));
+ assertEquals("artist1", hollow.getArtistName());
+
+ assertEquals(PersistenceState.DELETED, hollow.getPersistenceState());
+ }
+
+ @Test
+ public void testCommitUnchangedInsert() throws Exception {
+
+ // see CAY-1444 - reproducible on DB's that support auto incremented PK
+
+ NullTestEntity newObject = context.newObject(NullTestEntity.class);
+
+ assertTrue(context.hasChanges());
+ context.commitChanges();
+ assertFalse(context.hasChanges());
+
+ assertEquals(PersistenceState.COMMITTED, newObject.getPersistenceState());
}
-
- @Test
- public void testIterateDataRows() throws Exception {
-
- createArtistsDataSet();
-
- SelectQuery<DataRow> q1 = SelectQuery.dataRowQuery(Artist.class, null);
- final int[] count = new int[1];
-
- context.iterate(q1, new ResultIteratorCallback<DataRow>() {
-
- @Override
- public void next(DataRow object) {
- assertNotNull(object.get("ARTIST_ID"));
- count[0]++;
- }
- });
-
- assertEquals(7, count[0]);
- }
-
- @Test
- public void testIterator() throws Exception {
-
- createArtistsDataSet();
-
- SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
-
- ResultIterator<Artist> it = context.iterator(q1);
- try {
- int count = 0;
-
- for (Artist a : it) {
- count++;
- }
-
- assertEquals(7, count);
- } finally {
- it.close();
- }
- }
-
- @Test
- public void testBatchIterator() throws Exception {
- createLargeArtistsDataSet();
-
- SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
- ResultBatchIterator<Artist> it = context.batchIterator(q1, 5);
-
- try {
- int count = 0;
-
- for (List<Artist> artistList : it) {
- count++;
- assertEquals(5, artistList.size());
- }
-
- assertEquals(4, count);
- } finally {
- it.close();
- }
- }
-
- @Test
- public void testPerformIteratedQuery1() throws Exception {
-
- createArtistsDataSet();
-
- SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
- ResultIterator<?> it = context.performIteratedQuery(q1);
-
- try {
- int count = 0;
- while (it.hasNextRow()) {
- it.nextRow();
- count++;
- }
-
- assertEquals(7, count);
- } finally {
- it.close();
- }
- }
-
- @Test
- public void testPerformIteratedQuery2() throws Exception {
- createArtistsAndPaintingsDataSet();
-
- ResultIterator it = context.performIteratedQuery(new SelectQuery(Artist.class));
-
- // just for this test increase pool size
- changeMaxConnections(1);
-
- try {
- while (it.hasNextRow()) {
- DataRow row = (DataRow) it.nextRow();
-
- // try instantiating an object and fetching its relationships
- Artist artist = context.objectFromDataRow(Artist.class, row);
- List<?> paintings = artist.getPaintingArray();
- assertNotNull(paintings);
- assertEquals("Expected one painting for artist: " + artist, 1, paintings.size());
- }
- } finally {
- // change allowed connections back
- changeMaxConnections(-1);
- it.close();
- }
- }
-
- /**
- * Tests that hasChanges performs correctly when an object is "modified" and
- * the property is simply set to the same value (an unreal modification)
- */
- @Test
- public void testHasChangesPhantom() {
-
- String artistName = "ArtistName";
- Artist artist = (Artist) context.newObject("Artist");
- artist.setArtistName(artistName);
- context.commitChanges();
-
- // Set again to *exactly* the same value
- artist.setArtistName(artistName);
-
- // note that since 1.2 the polciy is for hasChanges to return true for
- // phantom
- // modifications, as there is no way to detect some more subtle
- // modifications like
- // a change of the master related object, until we actually create the
- // PKs
- assertTrue(context.hasChanges());
- }
-
- /**
- * Tests that hasChanges performs correctly when an object is "modified" and
- * the property is simply set to the same value (an unreal modification)
- */
- @Test
- public void testHasChangesRealModify() {
- Artist artist = (Artist) context.newObject("Artist");
- artist.setArtistName("ArtistName");
- context.commitChanges();
-
- artist.setArtistName("Something different");
- assertTrue(context.hasChanges());
- }
-
- @Test
- public void testInvalidateObjects_Vararg() throws Exception {
-
- DataRow row = new DataRow(10);
- row.put("ARTIST_ID", new Integer(1));
- row.put("ARTIST_NAME", "ArtistXYZ");
- row.put("DATE_OF_BIRTH", new Date());
- DataObject object = context.objectFromDataRow(Artist.class, row);
- ObjectId oid = object.getObjectId();
-
- // insert object into the ObjectStore
- context.getObjectStore().registerNode(oid, object);
-
- assertSame(object, context.getObjectStore().getNode(oid));
- assertNotNull(context.getObjectStore().getCachedSnapshot(oid));
-
- context.invalidateObjects(object);
-
- assertSame(oid, object.getObjectId());
- assertNull(context.getObjectStore().getCachedSnapshot(oid));
- assertSame(object, context.getObjectStore().getNode(oid));
- }
-
- @Test
- public void testInvalidateObjects() throws Exception {
-
- DataRow row = new DataRow(10);
- row.put("ARTIST_ID", new Integer(1));
- row.put("ARTIST_NAME", "ArtistXYZ");
- row.put("DATE_OF_BIRTH", new Date());
- DataObject object = context.objectFromDataRow(Artist.class, row);
- ObjectId oid = object.getObjectId();
-
- // insert object into the ObjectStore
- context.getObjectStore().registerNode(oid, object);
-
- assertSame(object, context.getObjectStore().getNode(oid));
- assertNotNull(context.getObjectStore().getCachedSnapshot(oid));
-
- context.invalidateObjects(Collections.singleton(object));
-
- assertSame(oid, object.getObjectId());
- assertNull(context.getObjectStore().getCachedSnapshot(oid));
- assertSame(object, context.getObjectStore().getNode(oid));
- }
-
- @Test
- public void testBeforeHollowDeleteShouldChangeStateToCommited() throws Exception {
- createSingleArtistDataSet();
-
- Artist hollow = Cayenne.objectForPK(context, Artist.class, 33001);
- context.invalidateObjects(hollow);
- assertEquals(PersistenceState.HOLLOW, hollow.getPersistenceState());
-
- // testing this...
- context.deleteObjects(hollow);
- assertSame(hollow, context.getGraphManager().getNode(new ObjectId("Artist", "ARTIST_ID", 33001)));
- assertEquals("artist1", hollow.getArtistName());
-
- assertEquals(PersistenceState.DELETED, hollow.getPersistenceState());
- }
-
- @Test
- public void testCommitUnchangedInsert() throws Exception {
-
- // see CAY-1444 - reproducible on DB's that support auto incremented PK
-
- NullTestEntity newObject = context.newObject(NullTestEntity.class);
-
- assertTrue(context.hasChanges());
- context.commitChanges();
- assertFalse(context.hasChanges());
-
- assertEquals(PersistenceState.COMMITTED, newObject.getPersistenceState());
- }
-
- private void changeMaxConnections(int delta) {
- PoolManager manager = (PoolManager) dataSourceFactory.getSharedDataSource();
- manager.setMaxConnections(manager.getMaxConnections() + delta);
- }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/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
new file mode 100644
index 0000000..ef81d1a
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/conn/BasePoolingDataSourceIT.java
@@ -0,0 +1,74 @@
+/*****************************************************************
+ * 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/8b54c052/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolManagerIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolManagerIT.java b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolManagerIT.java
deleted file mode 100644
index 832470b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolManagerIT.java
+++ /dev/null
@@ -1,128 +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.apache.cayenne.di.Inject;
-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;
-
-import java.sql.Connection;
-
-import static org.junit.Assert.assertEquals;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class PoolManagerIT extends ServerCase {
-
- @Inject
- private DataSourceInfo dataSourceInfo;
-
- @Test
- public void testDataSourceUrl() throws Exception {
- String driverName = dataSourceInfo.getJdbcDriver();
- String url = dataSourceInfo.getDataSourceUrl();
-
- PoolManager pm = new PoolManager(driverName, url, 0, 3, "", "") {
-
- @Override
- protected void startMaintenanceThread() {
- }
- };
- assertEquals(url, pm.getDataSourceUrl());
- assertEquals(driverName, pm.getJdbcDriver());
- }
-
- @Test
- public void testPassword() throws Exception {
- PoolManager pm = new PoolManager(null, 0, 3, "", "b") {
-
- @Override
- protected void startMaintenanceThread() {
- }
- };
- assertEquals("b", pm.getPassword());
- }
-
- @Test
- public void testUserName() throws Exception {
- PoolManager pm = new PoolManager(null, 0, 3, "a", "") {
-
- @Override
- protected void startMaintenanceThread() {
- }
- };
- assertEquals("a", pm.getUserName());
- }
-
- @Test
- public void testMinConnections() throws Exception {
- PoolManager pm = new PoolManager(null, 0, 3, "", "") {
-
- @Override
- protected void startMaintenanceThread() {
- }
- };
- assertEquals(0, pm.getMinConnections());
- }
-
- @Test
- public void testMaxConnections() throws Exception {
- PoolManager pm = new PoolManager(null, 0, 3, "", "") {
-
- @Override
- protected void startMaintenanceThread() {
- }
- };
- assertEquals(3, pm.getMaxConnections());
- }
-
- @Test
- public void testPooling() throws Exception {
-
- PoolManager pm = new PoolManager(dataSourceInfo.getJdbcDriver(), dataSourceInfo
- .getDataSourceUrl(), 2, 3, dataSourceInfo.getUserName(), dataSourceInfo
- .getPassword());
-
- try {
- assertEquals(0, pm.getCurrentlyInUse());
- assertEquals(2, pm.getCurrentlyUnused());
-
- Connection c1 = pm.getConnection();
- assertEquals(1, pm.getCurrentlyInUse());
- assertEquals(1, pm.getCurrentlyUnused());
-
- Connection c2 = pm.getConnection();
- assertEquals(2, pm.getCurrentlyInUse());
- assertEquals(0, pm.getCurrentlyUnused());
-
- c1.close();
- assertEquals(1, pm.getCurrentlyInUse());
- assertEquals(1, pm.getCurrentlyUnused());
-
- c2.close();
- assertEquals(0, pm.getCurrentlyInUse());
- assertEquals(2, pm.getCurrentlyUnused());
- }
- finally {
- pm.shutdown();
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/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
index e829372..829ceb6 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/conn/PooledConnectionImplTest.java
@@ -19,35 +19,49 @@
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 {
- @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.
- PoolManager pm = new PoolManager(null, 0, 3, "", "") {
- @Override
- protected void startMaintenanceThread() {}
- };
- PooledConnectionImpl con = new PooledConnectionImpl();
- con.addConnectionEventListener(pm);
- 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.
- PoolManager pm = new PoolManager(null, 0, 3, "", "") {
- @Override
- protected void startMaintenanceThread() {}
- };
- PooledConnectionImpl con = new PooledConnectionImpl();
- con.addConnectionEventListener(pm);
- con.connectionClosedNotification();
- }
+ 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/8b54c052/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
new file mode 100644
index 0000000..d2814d9
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSourceIT.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ * 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/8b54c052/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
new file mode 100644
index 0000000..d832ae5
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_FailingValidationQueryIT.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ * 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/8b54c052/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
new file mode 100644
index 0000000..45a5689
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/conn/PoolingDataSource_ValidationQueryIT.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ * 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/8b54c052/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 6859f2f..43ef5f3 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
@@ -18,7 +18,6 @@
****************************************************************/
package org.apache.cayenne.unit.di.server;
-import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -27,70 +26,54 @@ import java.util.Set;
import javax.sql.DataSource;
+import org.apache.cayenne.conn.DataSourceBuilder;
import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.PoolDataSource;
-import org.apache.cayenne.conn.PoolManager;
+import org.apache.cayenne.di.AdhocObjectFactory;
import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
-public class ServerCaseDataSourceFactory {
+public class ServerCaseDataSourceFactory {
- private DataSource sharedDataSource;
- private DataSourceInfo dataSourceInfo;
- private Map<String, DataSource> dataSources;
- private Set<String> mapsWithDedicatedDataSource;
+ private DataSource sharedDataSource;
+ private DataSourceInfo dataSourceInfo;
+ private Map<String, DataSource> dataSources;
+ private Set<String> mapsWithDedicatedDataSource;
+ private AdhocObjectFactory objectFactory;
+ private JdbcEventLogger logger;
- public ServerCaseDataSourceFactory(@Inject DataSourceInfo dataSourceInfo) {
+ public ServerCaseDataSourceFactory(@Inject DataSourceInfo dataSourceInfo, @Inject AdhocObjectFactory objectFactory,
+ @Inject JdbcEventLogger logger) {
- this.dataSourceInfo = dataSourceInfo;
- this.dataSources = new HashMap<String, DataSource>();
- this.mapsWithDedicatedDataSource = new HashSet<String>(Arrays.asList(
- "map-db1",
- "map-db2"));
+ this.logger = logger;
+ this.objectFactory = objectFactory;
+ this.dataSourceInfo = dataSourceInfo;
+ this.dataSources = new HashMap<String, DataSource>();
+ this.mapsWithDedicatedDataSource = new HashSet<String>(Arrays.asList("map-db1", "map-db2"));
- this.sharedDataSource = createDataSource();
- }
+ this.sharedDataSource = createDataSource();
+ }
- public DataSource getSharedDataSource() {
- return sharedDataSource;
- }
+ public DataSource getSharedDataSource() {
+ return sharedDataSource;
+ }
- public DataSource getDataSource(String dataMapName) {
- DataSource ds = dataSources.get(dataMapName);
- if (ds == null) {
+ public DataSource getDataSource(String dataMapName) {
+ DataSource ds = dataSources.get(dataMapName);
+ if (ds == null) {
- ds = mapsWithDedicatedDataSource.contains(dataMapName)
- ? createDataSource()
- : sharedDataSource;
+ ds = mapsWithDedicatedDataSource.contains(dataMapName) ? createDataSource() : sharedDataSource;
- dataSources.put(dataMapName, ds);
- }
+ dataSources.put(dataMapName, ds);
+ }
- return ds;
- }
+ return ds;
+ }
- private DataSource createDataSource() {
- try {
- PoolDataSource poolDS = new PoolDataSource(
- dataSourceInfo.getJdbcDriver(),
- dataSourceInfo.getDataSourceUrl());
- return new PoolManager(
- poolDS,
- dataSourceInfo.getMinConnections(),
- dataSourceInfo.getMaxConnections(),
- dataSourceInfo.getUserName(),
- dataSourceInfo.getPassword(), PoolManager.MAX_QUEUE_WAIT_DEFAULT) {
-
- @Override
- public void shutdown() throws SQLException {
- // noop - make sure we are not shutdown by the test scope, but at the
- // same time PoolManager methods are exposed (so we can't wrap
- // PoolManager)
- }
- };
- }
- catch (Exception ex) {
- throw new RuntimeException("Can not create shared data source.", ex);
- }
- }
+ private DataSource createDataSource() {
+ return DataSourceBuilder.builder(objectFactory, logger).driver(dataSourceInfo.getJdbcDriver())
+ .url(dataSourceInfo.getDataSourceUrl()).userName(dataSourceInfo.getUserName())
+ .password(dataSourceInfo.getPassword()).minConnections(dataSourceInfo.getMinConnections())
+ .maxConnections(dataSourceInfo.getMaxConnections()).build();
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/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 2d270aa..fb1d0a8 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-2005 Improving generated DISTINCT performance on PostgreSQL
CAY-2007 Refactoring SelectTranslator for better extensibility
+CAY-2008 Connection pool refactoring and validation query support in Cayenne DataSource
Bug Fixes:
[2/2] cayenne git commit: CAY-2008 Connection pool refactoring and
validation query support in Cayenne DataSource
Posted by aa...@apache.org.
CAY-2008 Connection pool refactoring and validation query support in Cayenne DataSource
* Refactoring and cleanup of DataSource implementation
* DataSourceBuilder
* splitting pool management thread into a separate DS wrapper
* implementing java.sql.Wrapper methods
* pool specific parameters are passed as a special object
* stop supporting getting connections with expclicit username/password. We do not have any
notion of per-user connection pools, so it is only misleading and can lead
to unpredictable pool state
* validate query
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/8b54c052
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/8b54c052
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/8b54c052
Branch: refs/heads/master
Commit: 8b54c052d6e54a5218c049819e35d718b4049af0
Parents: 5e037fc
Author: aadamchik <aa...@apache.org>
Authored: Thu Apr 30 13:34:21 2015 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Thu Apr 30 18:46:25 2015 +0300
----------------------------------------------------------------------
.../apache/cayenne/di/ScopeEventListener.java | 19 +-
.../server/PropertyDataSourceFactory.java | 86 +-
.../server/XMLPoolingDataSourceFactory.java | 59 +-
.../apache/cayenne/conn/DataSourceBuilder.java | 135 ++
.../org/apache/cayenne/conn/DataSourceInfo.java | 74 +-
.../cayenne/conn/ManagedPoolingDataSource.java | 155 ++
.../org/apache/cayenne/conn/PoolDataSource.java | 90 -
.../org/apache/cayenne/conn/PoolManager.java | 649 --------
.../cayenne/conn/PooledConnectionFactory.java | 90 +
.../cayenne/conn/PooledConnectionImpl.java | 320 ++--
.../apache/cayenne/conn/PoolingDataSource.java | 471 ++++++
.../conn/PoolingDataSourceParameters.java | 64 +
.../apache/cayenne/access/DataContextIT.java | 1571 +++++++++---------
.../cayenne/conn/BasePoolingDataSourceIT.java | 74 +
.../org/apache/cayenne/conn/PoolManagerIT.java | 128 --
.../cayenne/conn/PooledConnectionImplTest.java | 68 +-
.../cayenne/conn/PoolingDataSourceIT.java | 72 +
...lingDataSource_FailingValidationQueryIT.java | 43 +
.../PoolingDataSource_ValidationQueryIT.java | 55 +
.../di/server/ServerCaseDataSourceFactory.java | 89 +-
docs/doc/src/main/resources/RELEASE-NOTES.txt | 1 +
21 files changed, 2296 insertions(+), 2017 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java b/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
index 171bd50..b6959ac 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/ScopeEventListener.java
@@ -18,18 +18,19 @@
****************************************************************/
package org.apache.cayenne.di;
-
/**
- * This interface duplicates default reflection based mechanism for receiving DI events.
- * It is not fully supported and its usage are reserved for cases when for some reason
- * it is not possible to use reflection. It is used for example in
- * {@link javax.sql.DataSource} managing layer to provide compatibility with java version 5.
+ * This interface duplicates default reflection based mechanism for receiving DI
+ * events. It is not fully supported and its usage are reserved for cases when
+ * for some reason it is not possible to use reflection. It is used for example
+ * in {@link javax.sql.DataSource} management layer to provide compatibility
+ * with java version 5.
*
* @since 3.1
*/
public interface ScopeEventListener {
- /**
- * Similar to {@link BeforeScopeEnd}
- */
- void beforeScopeEnd();
+
+ /**
+ * Similar to {@link BeforeScopeEnd}
+ */
+ void beforeScopeEnd();
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
index 237f4e4..87d18de 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PropertyDataSourceFactory.java
@@ -18,17 +18,14 @@
****************************************************************/
package org.apache.cayenne.configuration.server;
-import java.sql.Driver;
-
import javax.sql.DataSource;
import org.apache.cayenne.ConfigurationException;
import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.DataNodeDescriptor;
import org.apache.cayenne.configuration.RuntimeProperties;
-import org.apache.cayenne.conn.DriverDataSource;
-import org.apache.cayenne.conn.PoolDataSource;
-import org.apache.cayenne.conn.PoolManager;
+import org.apache.cayenne.conn.DataSourceBuilder;
+import org.apache.cayenne.conn.PoolingDataSource;
import org.apache.cayenne.di.AdhocObjectFactory;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.log.JdbcEventLogger;
@@ -52,57 +49,50 @@ import org.apache.cayenne.log.JdbcEventLogger;
*/
public class PropertyDataSourceFactory implements DataSourceFactory {
- @Inject
- protected RuntimeProperties properties;
-
- @Inject
- protected JdbcEventLogger jdbcEventLogger;
+ @Inject
+ protected RuntimeProperties properties;
- @Inject
- private AdhocObjectFactory objectFactory;
+ @Inject
+ protected JdbcEventLogger jdbcEventLogger;
- @Override
- public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws Exception {
+ @Inject
+ private AdhocObjectFactory objectFactory;
- String suffix = "." + nodeDescriptor.getDataChannelDescriptor().getName() + "." + nodeDescriptor.getName();
+ @Override
+ public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws Exception {
- String driverClass = getProperty(Constants.JDBC_DRIVER_PROPERTY, suffix);
- String url = getProperty(Constants.JDBC_URL_PROPERTY, suffix);
- String username = getProperty(Constants.JDBC_USERNAME_PROPERTY, suffix);
- String password = getProperty(Constants.JDBC_PASSWORD_PROPERTY, suffix);
- int minConnections = getIntProperty(Constants.JDBC_MIN_CONNECTIONS_PROPERTY, suffix, 1);
- int maxConnections = getIntProperty(Constants.JDBC_MAX_CONNECTIONS_PROPERTY, suffix, 1);
+ String suffix = "." + nodeDescriptor.getDataChannelDescriptor().getName() + "." + nodeDescriptor.getName();
- Driver driver = objectFactory.newInstance(Driver.class, driverClass);
- DriverDataSource driverDS = new DriverDataSource(driver, url, username, password);
- driverDS.setLogger(jdbcEventLogger);
- PoolDataSource poolDS = new PoolDataSource(driverDS);
+ String driverClass = getProperty(Constants.JDBC_DRIVER_PROPERTY, suffix);
+ String url = getProperty(Constants.JDBC_URL_PROPERTY, suffix);
+ String username = getProperty(Constants.JDBC_USERNAME_PROPERTY, suffix);
+ String password = getProperty(Constants.JDBC_PASSWORD_PROPERTY, suffix);
+ int minConnections = getIntProperty(Constants.JDBC_MIN_CONNECTIONS_PROPERTY, suffix, 1);
+ int maxConnections = getIntProperty(Constants.JDBC_MAX_CONNECTIONS_PROPERTY, suffix, 1);
+ long maxQueueWaitTime = properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
+ PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
- try {
- return new PoolManager(poolDS, minConnections, maxConnections, username, password, properties.getLong(
- Constants.SERVER_MAX_QUEUE_WAIT_TIME, PoolManager.MAX_QUEUE_WAIT_DEFAULT));
- } catch (Exception e) {
- jdbcEventLogger.logConnectFailure(e);
- throw e;
- }
- }
+ return DataSourceBuilder.builder(objectFactory, jdbcEventLogger).driver(driverClass).url(url)
+ .userName(username).password(password).minConnections(minConnections).maxConnections(maxConnections)
+ .maxQueueWaitTime(maxQueueWaitTime).build();
+ }
- protected int getIntProperty(String propertyName, String suffix, int defaultValue) {
- String string = getProperty(propertyName, suffix);
+ protected int getIntProperty(String propertyName, String suffix, int defaultValue) {
+ String string = getProperty(propertyName, suffix);
- if (string == null) {
- return defaultValue;
- }
+ if (string == null) {
+ return defaultValue;
+ }
- try {
- return Integer.parseInt(string);
- } catch (NumberFormatException e) {
- throw new ConfigurationException("Invalid int property '%s': '%s'", propertyName, string);
- }
- }
+ try {
+ return Integer.parseInt(string);
+ } catch (NumberFormatException e) {
+ throw new ConfigurationException("Invalid int property '%s': '%s'", propertyName, string);
+ }
+ }
- protected String getProperty(String propertyName, String suffix) {
- String value = properties.get(propertyName + suffix);
- return value != null ? value : properties.get(propertyName);
- }
+ protected String getProperty(String propertyName, String suffix) {
+ String value = properties.get(propertyName + suffix);
+ return value != null ? value : properties.get(propertyName);
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
index b3bad8d..a62a2d9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/XMLPoolingDataSourceFactory.java
@@ -18,18 +18,15 @@
****************************************************************/
package org.apache.cayenne.configuration.server;
-import java.sql.Driver;
-
import javax.sql.DataSource;
import org.apache.cayenne.ConfigurationException;
import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.DataNodeDescriptor;
import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.conn.DataSourceBuilder;
import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.conn.DriverDataSource;
-import org.apache.cayenne.conn.PoolDataSource;
-import org.apache.cayenne.conn.PoolManager;
+import org.apache.cayenne.conn.PoolingDataSource;
import org.apache.cayenne.di.AdhocObjectFactory;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.log.JdbcEventLogger;
@@ -47,43 +44,35 @@ import org.apache.commons.logging.LogFactory;
// something else?
public class XMLPoolingDataSourceFactory implements DataSourceFactory {
- private static final Log logger = LogFactory.getLog(XMLPoolingDataSourceFactory.class);
+ private static final Log logger = LogFactory.getLog(XMLPoolingDataSourceFactory.class);
- @Inject
- protected JdbcEventLogger jdbcEventLogger;
+ @Inject
+ protected JdbcEventLogger jdbcEventLogger;
- @Inject
- private RuntimeProperties properties;
+ @Inject
+ private RuntimeProperties properties;
- @Inject
- private AdhocObjectFactory objectFactory;
+ @Inject
+ private AdhocObjectFactory objectFactory;
- @Override
- public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws Exception {
+ @Override
+ public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws Exception {
- DataSourceInfo dataSourceDescriptor = nodeDescriptor.getDataSourceDescriptor();
+ DataSourceInfo descriptor = nodeDescriptor.getDataSourceDescriptor();
- if (dataSourceDescriptor == null) {
- String message = "Null dataSourceDescriptor for nodeDescriptor '" + nodeDescriptor.getName() + "'";
- logger.info(message);
- throw new ConfigurationException(message);
- }
+ if (descriptor == null) {
+ String message = "Null dataSourceDescriptor for nodeDescriptor '" + nodeDescriptor.getName() + "'";
+ logger.info(message);
+ throw new ConfigurationException(message);
+ }
- Driver driver = objectFactory.newInstance(Driver.class, dataSourceDescriptor.getJdbcDriver());
- DriverDataSource driverDS = new DriverDataSource(driver, dataSourceDescriptor.getDataSourceUrl(),
- dataSourceDescriptor.getUserName(), dataSourceDescriptor.getPassword());
- driverDS.setLogger(jdbcEventLogger);
- PoolDataSource poolDS = new PoolDataSource(driverDS);
+ long maxQueueWaitTime = properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
+ PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
- try {
- return new PoolManager(poolDS, dataSourceDescriptor.getMinConnections(),
- dataSourceDescriptor.getMaxConnections(), dataSourceDescriptor.getUserName(),
- dataSourceDescriptor.getPassword(), properties.getLong(Constants.SERVER_MAX_QUEUE_WAIT_TIME,
- PoolManager.MAX_QUEUE_WAIT_DEFAULT));
- } catch (Exception e) {
- jdbcEventLogger.logConnectFailure(e);
- throw e;
- }
- }
+ return DataSourceBuilder.builder(objectFactory, jdbcEventLogger).driver(descriptor.getJdbcDriver())
+ .url(descriptor.getDataSourceUrl()).userName(descriptor.getUserName())
+ .password(descriptor.getPassword()).minConnections(descriptor.getMinConnections())
+ .maxConnections(descriptor.getMaxConnections()).maxQueueWaitTime(maxQueueWaitTime).build();
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
new file mode 100644
index 0000000..c850d64
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceBuilder.java
@@ -0,0 +1,135 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.conn;
+
+import java.sql.Driver;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.log.NoopJdbcEventLogger;
+
+/**
+ * A builder class that creates a default Cayenne implementation of a pooling
+ * {@link DataSource}.
+ *
+ * @since 4.0
+ */
+public class DataSourceBuilder {
+
+ private AdhocObjectFactory objectFactory;
+ private JdbcEventLogger logger;
+ private String userName;
+ private String password;
+ private String driver;
+ private String url;
+ private PoolingDataSourceParameters poolParameters;
+
+ public static DataSourceBuilder builder(AdhocObjectFactory objectFactory, JdbcEventLogger logger) {
+ return new DataSourceBuilder(objectFactory, logger);
+ }
+
+ private DataSourceBuilder(AdhocObjectFactory objectFactory, JdbcEventLogger logger) {
+ this.objectFactory = objectFactory;
+ this.logger = logger;
+ this.logger = NoopJdbcEventLogger.getInstance();
+ this.poolParameters = new PoolingDataSourceParameters();
+
+ poolParameters.setMinConnections(1);
+ poolParameters.setMaxConnections(1);
+ poolParameters.setMaxQueueWaitTime(PoolingDataSource.MAX_QUEUE_WAIT_DEFAULT);
+ }
+
+ public DataSourceBuilder userName(String userName) {
+ this.userName = userName;
+ return this;
+ }
+
+ public DataSourceBuilder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public DataSourceBuilder driver(String driver) {
+ this.driver = driver;
+ return this;
+ }
+
+ public DataSourceBuilder url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ public DataSourceBuilder minConnections(int minConnections) {
+ poolParameters.setMinConnections(minConnections);
+ return this;
+ }
+
+ public DataSourceBuilder maxConnections(int maxConnections) {
+ poolParameters.setMaxConnections(maxConnections);
+ return this;
+ }
+
+ public DataSourceBuilder maxQueueWaitTime(long maxQueueWaitTime) {
+ poolParameters.setMaxQueueWaitTime(maxQueueWaitTime);
+ return this;
+ }
+
+ public DataSource build() {
+
+ // sanity checks...
+ if (poolParameters.getMaxConnections() < 0) {
+ throw new CayenneRuntimeException("Maximum number of connections can not be negative ("
+ + poolParameters.getMaxConnections() + ").");
+ }
+
+ if (poolParameters.getMinConnections() < 0) {
+ throw new CayenneRuntimeException("Minimum number of connections can not be negative ("
+ + poolParameters.getMinConnections() + ").");
+ }
+
+ if (poolParameters.getMinConnections() > poolParameters.getMaxConnections()) {
+ throw new CayenneRuntimeException("Minimum number of connections can not be bigger then maximum.");
+ }
+
+ DataSource nonPooling = buildNonPoolingDataSource();
+ return buildPoolingDataSource(new PooledConnectionFactory(nonPooling));
+ }
+
+ private DataSource buildNonPoolingDataSource() {
+ Driver driver = objectFactory.newInstance(Driver.class, this.driver);
+ DriverDataSource dataSource = new DriverDataSource(driver, url, userName, password);
+ dataSource.setLogger(logger);
+ return dataSource;
+ }
+
+ private DataSource buildPoolingDataSource(PooledConnectionFactory connectionFactory) {
+ PoolingDataSource poolDS;
+ try {
+ poolDS = new PoolingDataSource(connectionFactory, poolParameters);
+ } catch (Exception e) {
+ logger.logConnectFailure(e);
+ throw new CayenneRuntimeException("Error creating DataSource", e);
+ }
+
+ return new ManagedPoolingDataSource(poolDS);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
index 44c95c1..2bb2664 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/DataSourceInfo.java
@@ -39,6 +39,11 @@ public class DataSourceInfo implements Cloneable, Serializable, XMLSerializable
private static Log logger = LogFactory.getLog(DataSourceInfo.class);
+ public static final String PASSWORD_LOCATION_CLASSPATH = "classpath";
+ public static final String PASSWORD_LOCATION_EXECUTABLE = "executable";
+ public static final String PASSWORD_LOCATION_MODEL = "model";
+ public static final String PASSWORD_LOCATION_URL = "url";
+
protected String userName;
protected String password;
protected String jdbcDriver;
@@ -46,14 +51,6 @@ public class DataSourceInfo implements Cloneable, Serializable, XMLSerializable
protected String adapterClassName;
protected int minConnections = 1;
protected int maxConnections = 1;
-
- // Constants for passwordLocation
- public static final String PASSWORD_LOCATION_CLASSPATH = "classpath";
- public static final String PASSWORD_LOCATION_EXECUTABLE = "executable";
- public static final String PASSWORD_LOCATION_MODEL = "model";
- public static final String PASSWORD_LOCATION_URL = "url";
-
- // Extended parameters
protected String passwordEncoderClass = PlainTextPasswordEncoder.class.getName();
protected String passwordEncoderKey = "";
protected String passwordLocation = PASSWORD_LOCATION_MODEL;
@@ -64,43 +61,72 @@ public class DataSourceInfo implements Cloneable, Serializable, XMLSerializable
@Override
public boolean equals(Object obj) {
- if (obj == this)
+
+ if (obj == this) {
return true;
+ }
- if (obj == null)
+ if (obj == null) {
return false;
+ }
- if (obj.getClass() != this.getClass())
+ if (obj.getClass() != this.getClass()) {
return false;
+ }
DataSourceInfo dsi = (DataSourceInfo) obj;
- if (!Util.nullSafeEquals(this.userName, dsi.userName))
+ if (!Util.nullSafeEquals(this.userName, dsi.userName)) {
return false;
- if (!Util.nullSafeEquals(this.password, dsi.password))
+ }
+
+ if (!Util.nullSafeEquals(this.password, dsi.password)) {
return false;
- if (!Util.nullSafeEquals(this.jdbcDriver, dsi.jdbcDriver))
+ }
+
+ if (!Util.nullSafeEquals(this.jdbcDriver, dsi.jdbcDriver)) {
return false;
- if (!Util.nullSafeEquals(this.dataSourceUrl, dsi.dataSourceUrl))
+ }
+
+ if (!Util.nullSafeEquals(this.dataSourceUrl, dsi.dataSourceUrl)) {
return false;
- if (!Util.nullSafeEquals(this.adapterClassName, dsi.adapterClassName))
+ }
+
+ if (!Util.nullSafeEquals(this.adapterClassName, dsi.adapterClassName)) {
return false;
- if (this.minConnections != dsi.minConnections)
+ }
+
+ if (this.minConnections != dsi.minConnections) {
return false;
- if (this.maxConnections != dsi.maxConnections)
+ }
+
+ if (this.maxConnections != dsi.maxConnections) {
return false;
- if (!Util.nullSafeEquals(this.passwordEncoderClass, dsi.passwordEncoderClass))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordEncoderClass, dsi.passwordEncoderClass)) {
return false;
- if (!Util.nullSafeEquals(this.passwordEncoderKey, dsi.passwordEncoderKey))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordEncoderKey, dsi.passwordEncoderKey)) {
return false;
- if (!Util.nullSafeEquals(this.passwordSourceFilename, dsi.passwordSourceFilename))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordSourceFilename, dsi.passwordSourceFilename)) {
return false;
- if (!Util.nullSafeEquals(this.passwordSourceModel, dsi.passwordSourceModel))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordSourceModel, dsi.passwordSourceModel)) {
return false;
- if (!Util.nullSafeEquals(this.passwordSourceUrl, dsi.passwordSourceUrl))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordSourceUrl, dsi.passwordSourceUrl)) {
return false;
- if (!Util.nullSafeEquals(this.passwordLocation, dsi.passwordLocation))
+ }
+
+ if (!Util.nullSafeEquals(this.passwordLocation, dsi.passwordLocation)) {
return false;
+ }
return true;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
new file mode 100644
index 0000000..357c3e0
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/ManagedPoolingDataSource.java
@@ -0,0 +1,155 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.di.ScopeEventListener;
+
+/**
+ * A wrapper for {@link PoolingDataSourceManager} that manages the underlying
+ * connection pool size, shrinking it if needed.
+ *
+ * @since 4.0
+ */
+public class ManagedPoolingDataSource implements DataSource, ScopeEventListener {
+
+ private PoolingDataSourceManager dataSourceManager;
+ private PoolingDataSource dataSource;
+
+ public ManagedPoolingDataSource(PoolingDataSource dataSource) {
+
+ this.dataSource = dataSource;
+ this.dataSourceManager = new PoolingDataSourceManager();
+
+ dataSourceManager.start();
+ }
+
+ @Override
+ public void beforeScopeEnd() {
+ dataSourceManager.shouldStop();
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return dataSource.getConnection(username, password);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return dataSource.getLogWriter();
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return dataSource.getLoginTimeout();
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return (ManagedPoolingDataSource.class.equals(iface)) ? true : dataSource.isWrapperFor(iface);
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter arg0) throws SQLException {
+ dataSource.setLogWriter(arg0);
+ }
+
+ @Override
+ public void setLoginTimeout(int arg0) throws SQLException {
+ dataSource.setLoginTimeout(arg0);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ return ManagedPoolingDataSource.class.equals(iface) ? (T) this : dataSource.unwrap(iface);
+ }
+
+ // JDBC 4.1 compatibility under Java 1.6 and newer
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new UnsupportedOperationException();
+ }
+
+ boolean shouldShrinkPool() {
+ int unused = dataSource.getCurrentlyUnused();
+ int used = dataSource.getCurrentlyInUse();
+ int total = unused + used;
+ int median = dataSource.getMinConnections() + 1
+ + (dataSource.getMaxConnections() - dataSource.getMinConnections()) / 2;
+
+ return unused > 0 && total > median;
+ }
+
+ class PoolingDataSourceManager extends Thread {
+
+ private volatile boolean shouldStop;
+
+ PoolingDataSourceManager() {
+ setName("PoolManagerCleanup-" + dataSource.hashCode());
+ setDaemon(true);
+ this.shouldStop = false;
+ }
+
+ public void shouldStop() {
+ shouldStop = true;
+ interrupt();
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+
+ try {
+ // don't do it too often
+ Thread.sleep(600000);
+ } catch (InterruptedException iex) {
+ // ignore...
+ }
+
+ synchronized (dataSource) {
+
+ // simple pool management - close one connection if the
+ // count is
+ // above median and there are any idle connections.
+
+ if (shouldStop) {
+ break;
+ }
+
+ if (shouldShrinkPool()) {
+ dataSource.shrinkPool(1);
+ }
+ }
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
deleted file mode 100644
index ad0b33e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolDataSource.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.conn;
-
-import java.io.PrintWriter;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.logging.Logger;
-
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-import javax.sql.PooledConnection;
-
-/**
- * PoolDataSource allows to generate pooled connections.
- *
- * <p>
- * It is implemented as a wrapper around a non-pooled data source object.
- * Delegates all method calls except for "getPooledConnection" to the underlying
- * DataSource.
- *
- */
-public class PoolDataSource implements ConnectionPoolDataSource {
-
- private DataSource nonPooledDatasource;
-
- public PoolDataSource(DataSource nonPooledDatasource) {
- this.nonPooledDatasource = nonPooledDatasource;
- }
-
- public PoolDataSource(String jdbcDriver, String connectionUrl) throws SQLException {
- nonPooledDatasource = new DriverDataSource(jdbcDriver, connectionUrl);
- }
-
- @Override
- public int getLoginTimeout() throws SQLException {
- return nonPooledDatasource.getLoginTimeout();
- }
-
- @Override
- public void setLoginTimeout(int seconds) throws SQLException {
- nonPooledDatasource.setLoginTimeout(seconds);
- }
-
- @Override
- public PrintWriter getLogWriter() throws SQLException {
- return nonPooledDatasource.getLogWriter();
- }
-
- @Override
- public void setLogWriter(PrintWriter out) throws SQLException {
- nonPooledDatasource.setLogWriter(out);
- }
-
- @Override
- public PooledConnection getPooledConnection() throws SQLException {
- return new PooledConnectionImpl(nonPooledDatasource, null, null);
- }
-
- @Override
- public PooledConnection getPooledConnection(String user, String password) throws SQLException {
- return new PooledConnectionImpl(nonPooledDatasource, user, password);
- }
-
- /**
- * @since 3.1
- *
- * JDBC 4.1 compatibility under Java 1.7
- */
- public Logger getParentLogger() throws SQLFeatureNotSupportedException {
- throw new UnsupportedOperationException();
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
deleted file mode 100644
index 1a71024..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolManager.java
+++ /dev/null
@@ -1,649 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.conn;
-
-import java.io.PrintWriter;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.logging.Logger;
-
-import javax.sql.ConnectionEvent;
-import javax.sql.ConnectionEventListener;
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-import javax.sql.PooledConnection;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.di.ScopeEventListener;
-import org.apache.cayenne.log.JdbcEventLogger;
-
-/**
- * PoolManager is a Cayenne implementation of a pooling DataSource.
- */
-public class PoolManager implements ScopeEventListener, DataSource,
- ConnectionEventListener {
-
- /**
- * Defines a maximum time in milliseconds that a connection request could wait in the
- * connection queue. After this period expires, an exception will be thrown in the
- * calling method.
- */
- public static final int MAX_QUEUE_WAIT_DEFAULT = 20000;
-
- /**
- * An exception indicating that a connection request waiting in the queue
- * timed out and was unable to obtain a connection.
- */
- public static class ConnectionUnavailableException extends SQLException {
- private static final long serialVersionUID = 1063973806941023165L;
-
- public ConnectionUnavailableException(String message) {
- super(message);
- }
- }
-
- protected ConnectionPoolDataSource poolDataSource;
- protected int minConnections;
- protected int maxConnections;
- protected String dataSourceUrl;
- protected String jdbcDriver;
- protected String password;
- protected String userName;
-
- protected List<PooledConnection> unusedPool;
- protected List<PooledConnection> usedPool;
-
- private PoolMaintenanceThread poolMaintenanceThread;
-
- private boolean shuttingDown;
- private long maxQueueWaitTime;
-
- /**
- * Creates new PoolManager using org.apache.cayenne.conn.PoolDataSource for an
- * underlying ConnectionPoolDataSource.
- *
- * @deprecated since 4.0 This constructor causes implicit class loading that should avoided.
- */
- @Deprecated
- public PoolManager(String jdbcDriver, String dataSourceUrl, int minCons, int maxCons,
- String userName, String password) throws SQLException {
-
- this(jdbcDriver, dataSourceUrl, minCons, maxCons, userName, password, null, MAX_QUEUE_WAIT_DEFAULT);
- }
-
- /**
- * @deprecated since 4.0 This constructor causes implicit class loading that should avoided.
- */
- @Deprecated
- public PoolManager(String jdbcDriver, String dataSourceUrl, int minCons, int maxCons,
- String userName, String password, JdbcEventLogger logger, long maxQueueWaitTime) throws SQLException {
-
- if (logger != null) {
- DataSourceInfo info = new DataSourceInfo();
- info.setJdbcDriver(jdbcDriver);
- info.setDataSourceUrl(dataSourceUrl);
- info.setMinConnections(minCons);
- info.setMaxConnections(maxCons);
- info.setUserName(userName);
- info.setPassword(password);
- logger.logPoolCreated(info);
- }
-
- this.jdbcDriver = jdbcDriver;
- this.dataSourceUrl = dataSourceUrl;
- DriverDataSource driverDS = new DriverDataSource(jdbcDriver, dataSourceUrl);
- driverDS.setLogger(logger);
- PoolDataSource poolDS = new PoolDataSource(driverDS);
- init(poolDS, minCons, maxCons, userName, password, maxQueueWaitTime);
- }
-
- /**
- * Creates new PoolManager with the specified policy for connection pooling and a
- * ConnectionPoolDataSource object.
- *
- * @param poolDataSource data source for pooled connections
- * @param minCons Non-negative integer that specifies a minimum number of open
- * connections to keep in the pool at all times
- * @param maxCons Non-negative integer that specifies maximum number of simultaneuosly
- * open connections
- * @throws SQLException if pool manager can not be created.
- * @deprecated since 4.0 use {@link #PoolManager(ConnectionPoolDataSource, int, int, String, String, long)}
- */
- public PoolManager(ConnectionPoolDataSource poolDataSource, int minCons, int maxCons,
- String userName, String password) throws SQLException {
- this(poolDataSource, minCons, maxCons, userName, password, PoolManager.MAX_QUEUE_WAIT_DEFAULT);
- }
-
- /**
- * Creates new PoolManager with the specified policy for connection pooling and a
- * ConnectionPoolDataSource object.
- *
- * @param poolDataSource data source for pooled connections
- * @param minCons Non-negative integer that specifies a minimum number of open
- * connections to keep in the pool at all times
- * @param maxCons Non-negative integer that specifies maximum number of simultaneuosly
- * open connections
- * @throws SQLException if pool manager can not be created.
- * @since 4.0
- */
- public PoolManager(ConnectionPoolDataSource poolDataSource, int minCons, int maxCons,
- String userName, String password, long maxQueueWaitTime) throws SQLException {
- init(poolDataSource, minCons, maxCons, userName, password, maxQueueWaitTime);
- }
-
- /** Initializes pool. Normally called from constructor. */
- protected void init(
- ConnectionPoolDataSource poolDataSource,
- int minCons,
- int maxCons,
- String userName,
- String password,
- long maxQueueWaitTime) throws SQLException {
-
- // do sanity checks...
- if (maxConnections < 0) {
- throw new SQLException("Maximum number of connections can not be negative ("
- + maxCons
- + ").");
- }
-
- if (minConnections < 0) {
- throw new SQLException("Minimum number of connections can not be negative ("
- + minCons
- + ").");
- }
-
- if (minConnections > maxConnections) {
- throw new SQLException(
- "Minimum number of connections can not be bigger then maximum.");
- }
-
- // init properties
- this.userName = userName;
- this.password = password;
- this.minConnections = minCons;
- this.maxConnections = maxCons;
- this.poolDataSource = poolDataSource;
- this.maxQueueWaitTime = maxQueueWaitTime;
-
- // init pool... use linked lists to use the queue in the FIFO manner
- usedPool = new LinkedList<PooledConnection>();
- unusedPool = new LinkedList<PooledConnection>();
- growPool(minConnections, userName, password);
-
- startMaintenanceThread();
- }
-
- protected synchronized void startMaintenanceThread() {
- disposeOfMaintenanceThread();
- this.poolMaintenanceThread = new PoolMaintenanceThread(this);
- this.poolMaintenanceThread.start();
- }
-
- /**
- * Creates and returns new PooledConnection object, adding itself as a listener for
- * connection events.
- */
- protected PooledConnection newPooledConnection(String userName, String password)
- throws SQLException {
- PooledConnection connection = (userName != null) ? poolDataSource
- .getPooledConnection(userName, password) : poolDataSource
- .getPooledConnection();
- connection.addConnectionEventListener(this);
- return connection;
- }
-
- /**
- * Closes all existing connections, drains the pool and stops the maintenance thread.
- *
- * @since 3.1
- */
- public synchronized void shutdown() throws SQLException {
-
- // disposing maintenance thread first to avoid any changes to pools
- // during shutdown
- disposeOfMaintenanceThread();
-
- // using boolean variable instead of locking PoolManager instance due to
- // possible deadlock during shutdown when one of connections locks its
- // event listeners list trying to invoke locked PoolManager's listener methods
- shuttingDown = true;
-
- ListIterator<PooledConnection> unusedIterator = unusedPool.listIterator();
- while (unusedIterator.hasNext()) {
- PooledConnection con = unusedIterator.next();
- // close connection
- con.close();
- // remove connection from the list
- unusedIterator.remove();
- }
-
- // clean used connections
- ListIterator<PooledConnection> usedIterator = usedPool.listIterator();
- while (usedIterator.hasNext()) {
- PooledConnection con = usedIterator.next();
- // stop listening for connection events
- con.removeConnectionEventListener(this);
- // close connection
- con.close();
- // remove connection from the list
- usedIterator.remove();
- }
- }
-
- /**
- * An implementation of {@link ScopeEventListener} that simply calls
- * {@link #shutdown()}.
- *
- * @since 3.1
- */
- public void beforeScopeEnd() {
- try {
- shutdown();
- }
- catch (SQLException e) {
- throw new CayenneRuntimeException("Error while shutting down");
- }
- }
-
- protected void disposeOfMaintenanceThread() {
- if (poolMaintenanceThread != null) {
- poolMaintenanceThread.shutdown();
- poolMaintenanceThread = null;
- }
- }
-
- /**
- * @return true if at least one more connection can be added to the pool.
- */
- protected synchronized boolean canGrowPool() {
- return getPoolSize() < maxConnections;
- }
-
- /**
- * Increases connection pool by the specified number of connections.
- *
- * @return the actual number of created connections.
- * @throws SQLException if an error happens when creating a new connection.
- */
- protected synchronized int growPool(
- int addConnections,
- String userName,
- String password) throws SQLException {
-
- int i = 0;
- int startPoolSize = getPoolSize();
- for (; i < addConnections && startPoolSize + i < maxConnections; i++) {
- PooledConnection newConnection = newPooledConnection(userName, password);
- unusedPool.add(newConnection);
- }
-
- return i;
- }
-
- protected synchronized void shrinkPool(int closeConnections) {
- int idleSize = unusedPool.size();
- for (int i = 0; i < closeConnections && i < idleSize; i++) {
- PooledConnection con = unusedPool.remove(i);
-
- try {
- con.close();
- }
- catch (SQLException ex) {
- // ignore
- }
- }
- }
-
- /**
- * Returns maximum number of connections this pool can keep. This parameter when
- * configured allows to limit the number of simultaneously open connections.
- */
- public int getMaxConnections() {
- return maxConnections;
- }
-
- public void setMaxConnections(int maxConnections) {
- this.maxConnections = maxConnections;
- }
-
- /**
- * Returns the absolute minimum number of connections allowed in this pool at any
- * moment in time.
- */
- public int getMinConnections() {
- return minConnections;
- }
-
- public void setMinConnections(int minConnections) {
- this.minConnections = minConnections;
- }
-
- /**
- * Returns a database URL used to initialize this pool. Will return null if the pool
- * was initialized with ConnectionPoolDataSource.
- */
- public String getDataSourceUrl() {
- return dataSourceUrl;
- }
-
- /**
- * Returns a name of a JDBC driver used to initialize this pool. Will return null if
- * the pool was initialized with ConnectionPoolDataSource.
- */
- public String getJdbcDriver() {
- return jdbcDriver;
- }
-
- /** Returns a data source password used to initialize this pool. */
- public String getPassword() {
- return password;
- }
-
- /** Returns a data source user name used to initialize this pool. */
- public String getUserName() {
- return userName;
- }
-
- /**
- * Returns current number of connections.
- */
- public synchronized int getPoolSize() {
- return usedPool.size() + unusedPool.size();
- }
-
- /**
- * Returns the number of connections obtained via this DataSource that are currently
- * in use by the DataSource clients.
- */
- public synchronized int getCurrentlyInUse() {
- return usedPool.size();
- }
-
- /**
- * Returns the number of connections maintained in the pool that are currently not
- * used by any clients and are available immediately via <code>getConnection</code>
- * method.
- */
- public synchronized int getCurrentlyUnused() {
- return unusedPool.size();
- }
-
- /**
- * Returns connection from the pool using internal values of user name and password.
- * Equivalent to calling:
- * <p>
- * <code>ds.getConnection(ds.getUserName(), ds.getPassword())</code>
- * </p>
- */
- public Connection getConnection() throws SQLException {
- return getConnection(userName, password);
- }
-
- /** Returns connection from the pool. */
- public synchronized Connection getConnection(String userName, String password)
- throws SQLException {
-
- if (shuttingDown) {
- throw new SQLException("Pool manager is shutting down.");
- }
-
- PooledConnection pooledConnection = uncheckPooledConnection(userName, password);
-
- try {
- return uncheckConnection(pooledConnection);
- }
- catch (SQLException ex) {
-
- try {
- pooledConnection.close();
- }
- catch (SQLException ignored) {
- }
-
- // do one reconnect attempt...
- pooledConnection = uncheckPooledConnection(userName, password);
- try {
- return uncheckConnection(pooledConnection);
- }
- catch (SQLException reconnectEx) {
- try {
- pooledConnection.close();
- }
- catch (SQLException ignored) {
- }
-
- throw reconnectEx;
- }
- }
- }
-
- private Connection uncheckConnection(PooledConnection pooledConnection)
- throws SQLException {
- Connection c = pooledConnection.getConnection();
-
- // only do that on successfully unchecked connection...
- usedPool.add(pooledConnection);
- return c;
- }
-
- private PooledConnection uncheckPooledConnection(String userName, String password)
- throws SQLException {
- // wait for returned connections or the maintenance thread
- // to bump the pool size...
-
- if (unusedPool.size() == 0) {
-
- // first try to open a new connection
- if (canGrowPool()) {
- return newPooledConnection(userName, password);
- }
-
- // can't open no more... will have to wait for others to return a connection
-
- // note that if we were woken up
- // before the full wait period expired, and no connections are
- // available yet, go back to sleep. Otherwise we don't give a maintenance
- // thread a chance to increase pool size
- long waitTill = System.currentTimeMillis() + maxQueueWaitTime;
-
- do {
- try {
- wait(maxQueueWaitTime);
- }
- catch (InterruptedException iex) {
- // ignoring
- }
-
- } while (unusedPool.size() == 0 && (maxQueueWaitTime == 0 || waitTill > System.currentTimeMillis()));
-
- if (unusedPool.size() == 0) {
- throw new ConnectionUnavailableException(
- "Can't obtain connection. Request timed out. Total used connections: "
- + usedPool.size());
- }
- }
-
- // get first connection... lets cycle them in FIFO manner
- return unusedPool.remove(0);
- }
-
- public int getLoginTimeout() throws java.sql.SQLException {
- return poolDataSource.getLoginTimeout();
- }
-
- public void setLoginTimeout(int seconds) throws java.sql.SQLException {
- poolDataSource.setLoginTimeout(seconds);
- }
-
- public PrintWriter getLogWriter() throws java.sql.SQLException {
- return poolDataSource.getLogWriter();
- }
-
- public void setLogWriter(PrintWriter out) throws java.sql.SQLException {
- poolDataSource.setLogWriter(out);
- }
-
- /**
- * Returns closed connection to the pool.
- */
- public synchronized void connectionClosed(ConnectionEvent event) {
-
- if (shuttingDown) {
- return;
- }
-
- // return connection to the pool
- PooledConnection closedConn = (PooledConnection) event.getSource();
-
- // remove this connection from the list of connections
- // managed by this pool...
- int usedInd = usedPool.indexOf(closedConn);
- if (usedInd >= 0) {
- usedPool.remove(usedInd);
- unusedPool.add(closedConn);
-
- // notify threads waiting for connections
- notifyAll();
- }
- // else ....
- // other possibility is that this is a bad connection, so just ignore its closing
- // event,
- // since it was unregistered in "connectionErrorOccurred"
- }
-
- /**
- * Removes connection with an error from the pool. This method is called by
- * PoolManager connections on connection errors to notify PoolManager that connection
- * is in invalid state.
- */
- public synchronized void connectionErrorOccurred(ConnectionEvent event) {
-
- if (shuttingDown) {
- return;
- }
-
- // later on we should analyze the error to see if this
- // is fatal... right now just kill this PooledConnection
-
- PooledConnection errorSrc = (PooledConnection) event.getSource();
-
- // remove this connection from the list of connections
- // managed by this pool...
-
- int usedInd = usedPool.indexOf(errorSrc);
- if (usedInd >= 0) {
- usedPool.remove(usedInd);
- }
- else {
- int unusedInd = unusedPool.indexOf(errorSrc);
- if (unusedInd >= 0)
- unusedPool.remove(unusedInd);
- }
-
- // do not close connection,
- // let the code that catches the exception handle it
- // ....
- }
-
- static class PoolMaintenanceThread extends Thread {
-
- private boolean shouldDie;
- private PoolManager pool;
-
- PoolMaintenanceThread(PoolManager pool) {
- super.setName("PoolManagerCleanup-" + pool.hashCode());
- super.setDaemon(true);
- this.pool = pool;
- }
-
- @Override
- public void run() {
- // periodically wakes up to check if the pool should grow or shrink
- while (true) {
-
- try {
- // don't do it too often
- sleep(600000);
- }
- catch (InterruptedException iex) {
- // ignore...
- }
-
- synchronized (pool) {
- // TODO: implement a smarter algorithm for pool management...
- // right now it will simply close one connection if the count is
- // above median and there are any idle connections.
-
- if (shouldDie) {
- break;
- }
-
- int unused = pool.getCurrentlyUnused();
- int used = pool.getCurrentlyInUse();
- int total = unused + used;
- int median = pool.minConnections
- + 1
- + (pool.maxConnections - pool.minConnections)
- / 2;
-
- if (unused > 0 && total > median) {
- pool.shrinkPool(1);
- }
- }
- }
- }
-
- /**
- * Stops the maintenance thread.
- */
- void shutdown() {
- shouldDie = true;
- interrupt();
- }
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public boolean isWrapperFor(Class<?> iface) throws SQLException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public <T> T unwrap(Class<T> iface) throws SQLException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * @since 3.1 JDBC 4.1 compatibility under Java 1.5
- */
- public Logger getParentLogger() throws SQLFeatureNotSupportedException {
- throw new UnsupportedOperationException();
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
new file mode 100644
index 0000000..2ebfeab
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionFactory.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+import javax.sql.PooledConnection;
+
+/**
+ * A {@link ConnectionPoolDataSource} implementation.
+ * <p>
+ * It is implemented as a wrapper around a non-pooled data source object.
+ * Delegates all method calls except for "getPooledConnection" to the underlying
+ * DataSource.
+ *
+ * @since 4.0
+ */
+public class PooledConnectionFactory implements ConnectionPoolDataSource {
+
+ private DataSource nonPooledDatasource;
+
+ public PooledConnectionFactory(DataSource nonPooledDatasource) {
+ this.nonPooledDatasource = nonPooledDatasource;
+ }
+
+ public PooledConnectionFactory(String jdbcDriver, String connectionUrl) throws SQLException {
+ nonPooledDatasource = new DriverDataSource(jdbcDriver, connectionUrl);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return nonPooledDatasource.getLoginTimeout();
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ nonPooledDatasource.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return nonPooledDatasource.getLogWriter();
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ nonPooledDatasource.setLogWriter(out);
+ }
+
+ @Override
+ public PooledConnection getPooledConnection() throws SQLException {
+ return new PooledConnectionImpl(nonPooledDatasource, null, null);
+ }
+
+ @Override
+ public PooledConnection getPooledConnection(String user, String password) throws SQLException {
+ return new PooledConnectionImpl(nonPooledDatasource, user, password);
+ }
+
+ /**
+ * @since 3.1
+ *
+ * JDBC 4.1 compatibility under Java 1.7
+ */
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new UnsupportedOperationException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
index f63dcb8..eb327ff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/PooledConnectionImpl.java
@@ -32,170 +32,164 @@ import javax.sql.PooledConnection;
import javax.sql.StatementEventListener;
/**
- * PooledConnectionImpl is an implementation of a pooling wrapper for the database
- * connection as per JDBC3 spec. Most of the modern JDBC drivers should have its own
- * implementation that may be used instead of this class.
- *
+ * PooledConnectionImpl is an implementation of a pooling wrapper for the
+ * database connection as per JDBC3 spec. Most of the modern JDBC drivers should
+ * have its own implementation that may be used instead of this class.
*/
public class PooledConnectionImpl implements PooledConnection {
- private Connection connectionObj;
- private List<ConnectionEventListener> connectionEventListeners;
- private boolean hadErrors;
- private DataSource connectionSource;
- private String userName;
- private String password;
-
- protected PooledConnectionImpl() {
- // TODO: maybe remove synchronization and use
- // FastArrayList from commons-collections? After
- // all the only listener is usually pool manager.
- this.connectionEventListeners = Collections
- .synchronizedList(new ArrayList<ConnectionEventListener>(10));
- }
-
- /** Creates new PooledConnection */
- public PooledConnectionImpl(DataSource connectionSource, String userName,
- String password) {
-
- this();
-
- this.connectionSource = connectionSource;
- this.userName = userName;
- this.password = password;
-
- }
-
- public void reconnect() throws SQLException {
- if (connectionObj != null) {
- try {
- connectionObj.close();
- }
- catch (SQLException ex) {
- // ignore exception, since connection is expected
- // to be in a bad state
- }
- finally {
- connectionObj = null;
- }
- }
-
- connectionObj = (userName != null) ? connectionSource.getConnection(
- userName,
- password) : connectionSource.getConnection();
- }
-
- public void addConnectionEventListener(ConnectionEventListener listener) {
- synchronized (connectionEventListeners) {
- if (!connectionEventListeners.contains(listener))
- connectionEventListeners.add(listener);
- }
- }
-
- public void removeConnectionEventListener(ConnectionEventListener listener) {
- synchronized (connectionEventListeners) {
- connectionEventListeners.remove(listener);
- }
- }
-
- public void close() throws SQLException {
-
- synchronized (connectionEventListeners) {
- // remove all listeners
- connectionEventListeners.clear();
- }
-
- if (connectionObj != null) {
- try {
- connectionObj.close();
- }
- finally {
- connectionObj = null;
- }
- }
- }
-
- public Connection getConnection() throws SQLException {
- if (connectionObj == null) {
- reconnect();
- }
-
- // set autocommit to false to return connection
- // always in consistent state
- if (!connectionObj.getAutoCommit()) {
-
- try {
- connectionObj.setAutoCommit(true);
- }
- catch (SQLException sqlEx) {
- // try applying Sybase patch
- ConnectionWrapper.sybaseAutoCommitPatch(connectionObj, sqlEx, true);
- }
- }
-
- connectionObj.clearWarnings();
- return new ConnectionWrapper(connectionObj, this);
- }
-
- protected void returnConnectionToThePool() throws SQLException {
- // do not return to pool bad connections
- if (hadErrors)
- close();
- else
- // notify the listeners that connection is no longer used by application...
- this.connectionClosedNotification();
- }
-
- /**
- * This method creates and sents an event to listeners when an error occurs in the
- * underlying connection. Listeners can have special logic to analyze the error and do
- * things like closing this PooledConnection (if the error is fatal), etc...
- */
- public void connectionErrorNotification(SQLException exception) {
- // hint for later to avoid returning bad connections to the pool
- hadErrors = true;
-
- synchronized (connectionEventListeners) {
- if (connectionEventListeners.size() == 0)
- return;
-
- ConnectionEvent closedEvent = new ConnectionEvent(this, exception);
- for (final ConnectionEventListener nextListener : connectionEventListeners) {
- nextListener.connectionErrorOccurred(closedEvent);
- }
- }
- }
-
- /**
- * Creates and sends an event to listeners when a user closes java.sql.Connection
- * object belonging to this PooledConnection.
- */
- protected void connectionClosedNotification() {
- synchronized (connectionEventListeners) {
- if (connectionEventListeners.size() == 0)
- return;
-
- ConnectionEvent closedEvent = new ConnectionEvent(this);
-
- for (final ConnectionEventListener nextListener : connectionEventListeners) {
- nextListener.connectionClosed(closedEvent);
- }
- }
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public void addStatementEventListener(StatementEventListener listener) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public void removeStatementEventListener(StatementEventListener listener) {
- throw new UnsupportedOperationException();
- }
+ private Connection connectionObj;
+ private List<ConnectionEventListener> connectionEventListeners;
+ private boolean hadErrors;
+ private DataSource dataSource;
+ private String userName;
+ private String password;
+
+ public PooledConnectionImpl(DataSource dataSource, String userName, String password) {
+
+ // TODO: maybe remove synchronization and use
+ // FastArrayList from commons-collections? After
+ // all the only listener is usually pool manager.
+ this.connectionEventListeners = Collections.synchronizedList(new ArrayList<ConnectionEventListener>(10));
+
+ this.dataSource = dataSource;
+ this.userName = userName;
+ this.password = password;
+ }
+
+ public void reconnect() throws SQLException {
+ if (connectionObj != null) {
+ try {
+ connectionObj.close();
+ } catch (SQLException ex) {
+ // ignore exception, since connection is expected
+ // to be in a bad state
+ } finally {
+ connectionObj = null;
+ }
+ }
+
+ connectionObj = (userName != null) ? dataSource.getConnection(userName, password) : dataSource.getConnection();
+ }
+
+ @Override
+ public void addConnectionEventListener(ConnectionEventListener listener) {
+ synchronized (connectionEventListeners) {
+ if (!connectionEventListeners.contains(listener)) {
+ connectionEventListeners.add(listener);
+ }
+ }
+ }
+
+ @Override
+ public void removeConnectionEventListener(ConnectionEventListener listener) {
+ synchronized (connectionEventListeners) {
+ connectionEventListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void close() throws SQLException {
+
+ synchronized (connectionEventListeners) {
+ // remove all listeners
+ connectionEventListeners.clear();
+ }
+
+ if (connectionObj != null) {
+ try {
+ connectionObj.close();
+ } finally {
+ connectionObj = null;
+ }
+ }
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (connectionObj == null) {
+ reconnect();
+ }
+
+ // set autocommit to false to return connection
+ // always in consistent state
+ if (!connectionObj.getAutoCommit()) {
+
+ try {
+ connectionObj.setAutoCommit(true);
+ } catch (SQLException sqlEx) {
+ // try applying Sybase patch
+ ConnectionWrapper.sybaseAutoCommitPatch(connectionObj, sqlEx, true);
+ }
+ }
+
+ connectionObj.clearWarnings();
+ return new ConnectionWrapper(connectionObj, this);
+ }
+
+ protected void returnConnectionToThePool() throws SQLException {
+ // do not return to pool bad connections
+ if (hadErrors)
+ close();
+ else
+ // notify the listeners that connection is no longer used by
+ // application...
+ this.connectionClosedNotification();
+ }
+
+ /**
+ * This method creates and sents an event to listeners when an error occurs
+ * in the underlying connection. Listeners can have special logic to analyze
+ * the error and do things like closing this PooledConnection (if the error
+ * is fatal), etc...
+ */
+ public void connectionErrorNotification(SQLException exception) {
+ // hint for later to avoid returning bad connections to the pool
+ hadErrors = true;
+
+ synchronized (connectionEventListeners) {
+ if (connectionEventListeners.isEmpty()) {
+ return;
+ }
+
+ ConnectionEvent closedEvent = new ConnectionEvent(this, exception);
+ for (ConnectionEventListener nextListener : connectionEventListeners) {
+ nextListener.connectionErrorOccurred(closedEvent);
+ }
+ }
+ }
+
+ /**
+ * Creates and sends an event to listeners when a user closes
+ * java.sql.Connection object belonging to this PooledConnection.
+ */
+ protected void connectionClosedNotification() {
+ synchronized (connectionEventListeners) {
+ if (connectionEventListeners.size() == 0) {
+ return;
+ }
+
+ ConnectionEvent closedEvent = new ConnectionEvent(this);
+
+ for (ConnectionEventListener nextListener : connectionEventListeners) {
+ nextListener.connectionClosed(closedEvent);
+ }
+ }
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public void addStatementEventListener(StatementEventListener listener) {
+ throw new UnsupportedOperationException("Statement events are unsupported");
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public void removeStatementEventListener(StatementEventListener listener) {
+ throw new UnsupportedOperationException("Statement events are unsupported");
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
new file mode 100644
index 0000000..cccf905
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSource.java
@@ -0,0 +1,471 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.conn;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.logging.Logger;
+
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+import javax.sql.PooledConnection;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.di.ScopeEventListener;
+
+/**
+ * A {@link DataSource} with a pool of connections, that can automatically grow
+ * to the max size as more connections are requested.
+ *
+ * @since 4.0
+ */
+public class PoolingDataSource implements ScopeEventListener, DataSource, ConnectionEventListener {
+
+ /**
+ * Defines a maximum time in milliseconds that a connection request could
+ * wait in the connection queue. After this period expires, an exception
+ * will be thrown in the calling method.
+ */
+ public static final int MAX_QUEUE_WAIT_DEFAULT = 20000;
+
+ /**
+ * An exception indicating that a connection request waiting in the queue
+ * timed out and was unable to obtain a connection.
+ */
+ public static class ConnectionUnavailableException extends SQLException {
+ private static final long serialVersionUID = 1063973806941023165L;
+
+ public ConnectionUnavailableException(String message) {
+ super(message);
+ }
+ }
+
+ protected ConnectionPoolDataSource pooledConnectionFactory;
+
+ private int minConnections;
+ private int maxConnections;
+ private long maxQueueWaitTime;
+ private String validationQuery;
+
+ private List<PooledConnection> unusedPool;
+ private List<PooledConnection> usedPool;
+ private boolean shuttingDown;
+
+ /**
+ * Creates new PoolManager with the specified policy for connection pooling
+ * and a ConnectionPoolDataSource object.
+ *
+ * @param poolDataSource
+ * data source for pooled connections
+ * @param minCons
+ * Non-negative integer that specifies a minimum number of open
+ * connections to keep in the pool at all times
+ * @param maxCons
+ * Non-negative integer that specifies maximum number of
+ * simultaneously open connections
+ * @throws SQLException
+ * if pool manager can not be created.
+ * @since 4.0
+ */
+ public PoolingDataSource(ConnectionPoolDataSource poolDataSource, PoolingDataSourceParameters parameters)
+ throws SQLException {
+
+ this.pooledConnectionFactory = poolDataSource;
+
+ // clone parameters to keep DataSource immutable
+ this.minConnections = parameters.getMinConnections();
+ this.maxConnections = parameters.getMaxConnections();
+ this.maxQueueWaitTime = parameters.getMaxQueueWaitTime();
+ this.validationQuery = parameters.getValidationQuery();
+
+ // init pool... use linked lists to use the queue in the FIFO manner
+ this.usedPool = new LinkedList<PooledConnection>();
+ this.unusedPool = new LinkedList<PooledConnection>();
+ growPool(minConnections);
+ }
+
+ /**
+ * Creates and returns new PooledConnection object, adding itself as a
+ * listener for connection events.
+ */
+ protected PooledConnection newPooledConnection() throws SQLException {
+ PooledConnection connection = pooledConnectionFactory.getPooledConnection();
+ connection.addConnectionEventListener(this);
+ return connection;
+ }
+
+ /**
+ * Shuts down the pool, closing all open connections. This is an
+ * implementation of {@link ScopeEventListener}.
+ *
+ * @since 3.1
+ */
+ @Override
+ public synchronized void beforeScopeEnd() {
+ try {
+
+ // using boolean variable instead of locking PoolManager instance
+ // due to possible deadlock during shutdown when one of connections
+ // locks its event listeners list trying to invoke locked
+ // PoolManager's listener methods
+ shuttingDown = true;
+
+ ListIterator<PooledConnection> unusedIterator = unusedPool.listIterator();
+ while (unusedIterator.hasNext()) {
+ PooledConnection con = unusedIterator.next();
+ // close connection
+ con.close();
+ // remove connection from the list
+ unusedIterator.remove();
+ }
+
+ // clean used connections
+ ListIterator<PooledConnection> usedIterator = usedPool.listIterator();
+ while (usedIterator.hasNext()) {
+ PooledConnection con = usedIterator.next();
+ // stop listening for connection events
+ con.removeConnectionEventListener(this);
+ // close connection
+ con.close();
+ // remove connection from the list
+ usedIterator.remove();
+ }
+ } catch (SQLException e) {
+ throw new CayenneRuntimeException("Error while shutting down");
+ }
+ }
+
+ /**
+ * @return true if at least one more connection can be added to the pool.
+ */
+ protected synchronized boolean canGrowPool() {
+ return getPoolSize() < maxConnections;
+ }
+
+ /**
+ * Increases connection pool by the specified number of connections.
+ *
+ * @return the actual number of created connections.
+ * @throws SQLException
+ * if an error happens when creating a new connection.
+ */
+ protected synchronized int growPool(int addConnections) throws SQLException {
+
+ int i = 0;
+ int startPoolSize = getPoolSize();
+ for (; i < addConnections && startPoolSize + i < maxConnections; i++) {
+ PooledConnection newConnection = newPooledConnection();
+ unusedPool.add(newConnection);
+ }
+
+ return i;
+ }
+
+ public synchronized void shrinkPool(int closeConnections) {
+ int idleSize = unusedPool.size();
+ for (int i = 0; i < closeConnections && i < idleSize; i++) {
+ PooledConnection con = unusedPool.remove(i);
+
+ try {
+ con.close();
+ } catch (SQLException ex) {
+ // ignore
+ }
+ }
+ }
+
+ public String getValidationQuery() {
+ return validationQuery;
+ }
+
+ /**
+ * Returns maximum number of connections this pool can keep. This parameter
+ * when configured allows to limit the number of simultaneously open
+ * connections.
+ */
+ public int getMaxConnections() {
+ return maxConnections;
+ }
+
+ /**
+ * Returns the absolute minimum number of connections allowed in this pool
+ * at any moment in time.
+ */
+ public int getMinConnections() {
+ return minConnections;
+ }
+
+ /**
+ * Returns current number of connections.
+ */
+ public synchronized int getPoolSize() {
+ return usedPool.size() + unusedPool.size();
+ }
+
+ /**
+ * Returns the number of connections obtained via this DataSource that are
+ * currently in use by the DataSource clients.
+ */
+ public synchronized int getCurrentlyInUse() {
+ return usedPool.size();
+ }
+
+ /**
+ * Returns the number of connections maintained in the pool that are
+ * currently not used by any clients and are available immediately via
+ * <code>getConnection</code> method.
+ */
+ public synchronized int getCurrentlyUnused() {
+ return unusedPool.size();
+ }
+
+ /**
+ * Returns connection from the pool using internal values of user name and
+ * password.
+ */
+ @Override
+ public synchronized Connection getConnection() throws SQLException {
+ if (shuttingDown) {
+ throw new SQLException("Pool manager is shutting down.");
+ }
+
+ PooledConnection pooledConnection = uncheckPooledConnection();
+
+ try {
+ return uncheckAndValidateConnection(pooledConnection);
+ } catch (SQLException ex) {
+
+ try {
+ pooledConnection.close();
+ } catch (SQLException ignored) {
+ }
+
+ // do one reconnect attempt...
+ pooledConnection = uncheckPooledConnection();
+ try {
+ return uncheckAndValidateConnection(pooledConnection);
+ } catch (SQLException reconnectEx) {
+ try {
+ pooledConnection.close();
+ } catch (SQLException ignored) {
+ }
+
+ throw reconnectEx;
+ }
+ }
+ }
+
+ /**
+ * Returns connection from the pool.
+ */
+ @Override
+ public synchronized Connection getConnection(String userName, String password) throws SQLException {
+ throw new UnsupportedOperationException(
+ "Connections for a specific user are not supported by the pooled DataSource");
+ }
+
+ private Connection uncheckConnection(PooledConnection pooledConnection) throws SQLException {
+ Connection c = pooledConnection.getConnection();
+
+ // only do that on successfully unchecked connection...
+ usedPool.add(pooledConnection);
+ return c;
+ }
+
+ private Connection uncheckAndValidateConnection(PooledConnection pooledConnection) throws SQLException {
+ Connection c = uncheckConnection(pooledConnection);
+
+ if (validationQuery != null) {
+
+ Statement statement = c.createStatement();
+ try {
+ ResultSet rs = statement.executeQuery(validationQuery);
+ try {
+
+ if (!rs.next()) {
+ throw new SQLException("Connection validation failed, no result for query: " + validationQuery);
+ }
+
+ } finally {
+ rs.close();
+ }
+ } finally {
+ statement.close();
+ }
+ }
+
+ return c;
+ }
+
+ private PooledConnection uncheckPooledConnection() throws SQLException {
+ // wait for returned connections or the maintenance thread
+ // to bump the pool size...
+
+ if (unusedPool.size() == 0) {
+
+ // first try to open a new connection
+ if (canGrowPool()) {
+ return newPooledConnection();
+ }
+
+ // can't open no more... will have to wait for others to return a
+ // connection
+
+ // note that if we were woken up
+ // before the full wait period expired, and no connections are
+ // available yet, go back to sleep. Otherwise we don't give a
+ // maintenance
+ // thread a chance to increase pool size
+ long waitTill = System.currentTimeMillis() + maxQueueWaitTime;
+
+ do {
+ try {
+ wait(maxQueueWaitTime);
+ } catch (InterruptedException iex) {
+ // ignoring
+ }
+
+ } while (unusedPool.size() == 0 && (maxQueueWaitTime == 0 || waitTill > System.currentTimeMillis()));
+
+ if (unusedPool.size() == 0) {
+ throw new ConnectionUnavailableException(
+ "Can't obtain connection. Request timed out. Total used connections: " + usedPool.size());
+ }
+ }
+
+ // get first connection... lets cycle them in FIFO manner
+ return unusedPool.remove(0);
+ }
+
+ @Override
+ public int getLoginTimeout() throws java.sql.SQLException {
+ return pooledConnectionFactory.getLoginTimeout();
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws java.sql.SQLException {
+ pooledConnectionFactory.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws java.sql.SQLException {
+ return pooledConnectionFactory.getLogWriter();
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws java.sql.SQLException {
+ pooledConnectionFactory.setLogWriter(out);
+ }
+
+ /**
+ * Returns closed connection to the pool.
+ */
+ @Override
+ public synchronized void connectionClosed(ConnectionEvent event) {
+
+ if (shuttingDown) {
+ return;
+ }
+
+ // return connection to the pool
+ PooledConnection closedConn = (PooledConnection) event.getSource();
+
+ // remove this connection from the list of connections
+ // managed by this pool...
+ int usedInd = usedPool.indexOf(closedConn);
+ if (usedInd >= 0) {
+ usedPool.remove(usedInd);
+ unusedPool.add(closedConn);
+
+ // notify threads waiting for connections
+ notifyAll();
+ }
+ // else ....
+ // other possibility is that this is a bad connection, so just ignore
+ // its closing
+ // event,
+ // since it was unregistered in "connectionErrorOccurred"
+ }
+
+ /**
+ * Removes connection with an error from the pool. This method is called by
+ * PoolManager connections on connection errors to notify PoolManager that
+ * connection is in invalid state.
+ */
+ @Override
+ public synchronized void connectionErrorOccurred(ConnectionEvent event) {
+
+ if (shuttingDown) {
+ return;
+ }
+
+ // later on we should analyze the error to see if this
+ // is fatal... right now just kill this PooledConnection
+
+ PooledConnection errorSrc = (PooledConnection) event.getSource();
+
+ // remove this connection from the list of connections
+ // managed by this pool...
+
+ int usedInd = usedPool.indexOf(errorSrc);
+ if (usedInd >= 0) {
+ usedPool.remove(usedInd);
+ } else {
+ int unusedInd = unusedPool.indexOf(errorSrc);
+ if (unusedInd >= 0) {
+ unusedPool.remove(unusedInd);
+ }
+ }
+
+ // do not close connection,
+ // let the code that catches the exception handle it
+ // ....
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return PoolingDataSource.class.equals(iface);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ if (PoolingDataSource.class.equals(iface)) {
+ return (T) this;
+ }
+
+ throw new SQLException("Not a wrapper for " + iface);
+ }
+
+ // JDBC 4.1 compatibility under Java <= 1.6
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new UnsupportedOperationException();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b54c052/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
new file mode 100644
index 0000000..a03fd39
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/conn/PoolingDataSourceParameters.java
@@ -0,0 +1,64 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.conn;
+
+/**
+ * A connection of pooling parameters used by {@link PoolingDataSource}.
+ *
+ * @since 4.0
+ */
+public class PoolingDataSourceParameters {
+
+ private String validationQuery;
+ private int minConnections;
+ private int maxConnections;
+ private long maxQueueWaitTime;
+
+ public int getMinConnections() {
+ return minConnections;
+ }
+
+ public void setMinConnections(int minConnections) {
+ this.minConnections = minConnections;
+ }
+
+ public int getMaxConnections() {
+ return maxConnections;
+ }
+
+ public void setMaxConnections(int maxConnections) {
+ this.maxConnections = maxConnections;
+ }
+
+ public long getMaxQueueWaitTime() {
+ return maxQueueWaitTime;
+ }
+
+ public void setMaxQueueWaitTime(long maxQueueWaitTime) {
+ this.maxQueueWaitTime = maxQueueWaitTime;
+ }
+
+ public String getValidationQuery() {
+ return validationQuery;
+ }
+
+ public void setValidationQuery(String validationQuery) {
+ this.validationQuery = validationQuery;
+ }
+}