/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.jdbc; import com.mockrunner.mock.jdbc.JDBCMockObjectFactory; import com.mockrunner.mock.jdbc.MockConnection; import com.mockrunner.mock.jdbc.MockDatabaseMetaData; import com.mockrunner.mock.jdbc.MockResultSet; import com.mockrunner.mock.jdbc.MockStatement; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import org.geotools.data.Query; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Hints; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.junit.Test; import org.opengis.feature.type.GeometryDescriptor; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Dean Povey */ public class JDBCDataStoreTest { // Subtype of the requested mapping type. This is used in the mapping initialisation test // as it triggers the code path that previously led to a ConcurrentModificationException. public static class DateSubClass extends Timestamp { public DateSubClass(long time) { super(time); } } @Test public void testMappingInitialisationIsThreadSafe() throws InterruptedException, ExecutionException { // This test attempts to exercise a race condition on initialisation of mappings. It is caused when // two threads simultaneously try to access mapping of SQL types for a datastore. This // previously led to a ConcurrentModificationException. (c.f. GEOT-4506) int nThreads = Math.min(Runtime.getRuntime().availableProcessors(), 2); ExecutorService executorService = Executors.newFixedThreadPool(nThreads); for (int i = 0; i < 100; i++) { final CountDownLatch latch = new CountDownLatch(nThreads); final JDBCDataStore jdbcDataStore = new JDBCDataStore(); SQLDialect sqlDialect = new BasicSQLDialect(jdbcDataStore) { @Override public void encodeGeometryValue(Geometry value, int dimension, int srid, StringBuffer sql) throws IOException { } @Override public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { } @Override public Envelope decodeGeometryEnvelope(ResultSet rs, int column, Connection cx) throws SQLException, IOException { return null; } @Override public Geometry decodeGeometryValue(GeometryDescriptor descriptor, ResultSet rs, String column, GeometryFactory factory, Connection cx) throws IOException, SQLException { return null; } }; jdbcDataStore.setSQLDialect(sqlDialect); List<Future> futures = new ArrayList<>(); for (int j = 0; j < nThreads; j++) { Future f = executorService.submit(new Runnable() { @Override public void run() { try { // Get all the threads to the same point to increase the likelihood // of finding the issue latch.countDown(); latch.await(); } catch (InterruptedException e) { Thread.interrupted(); return; } jdbcDataStore.getMapping(DateSubClass.class); } }); futures.add(f); } for (Future future : futures) { future.get(); } } executorService.shutdown(); executorService.awaitTermination(30, TimeUnit.SECONDS); } @Test(expected=IOException.class) public void testCheckAllInsertedPositive() throws IOException { JDBCDataStore.checkAllInserted(new int[0], 0); JDBCDataStore.checkAllInserted(new int[] {1,1,1}, 3); JDBCDataStore.checkAllInserted(new int[] {3}, 3); JDBCDataStore.checkAllInserted(new int[] {1, 1, 0}, 3); } @Test public void testReaderCallback() throws Exception { JDBCReaderCallback callback = mock(JDBCReaderCallback.class); JDBCDataStore store = new JDBCDataStore(); store.setNamespaceURI("http://geotools.org"); store.setPrimaryKeyFinder(new PrimaryKeyFinder() { @Override public PrimaryKey getPrimaryKey(JDBCDataStore store, String schema, String table, Connection cx) throws SQLException { return new NullPrimaryKey(table); } }); store.setCallbackFactory(new JDBCCallbackFactory() { @Override public String getName() { return "mock"; } @Override public JDBCReaderCallback createReaderCallback() { return callback; } }); store.setFeatureFactory(CommonFactoryFinder.getFeatureFactory(null)); JDBCMockObjectFactory jdbcMock = new JDBCMockObjectFactory(); store.setDataSource(jdbcMock.getMockDataSource()); MockResultSet tableTypes = new MockResultSet("tableTypes"); tableTypes.addColumn("TABLE_TYPE", Arrays.asList("TABLE")); MockResultSet tables = new MockResultSet("tables"); tables.addColumn("TABLE_NAME", Arrays.asList("foo")); tables.addColumn("TABLE_SCHEM", Arrays.asList("")); MockDatabaseMetaData meta = new MockDatabaseMetaData(); meta.setTableTypes(tableTypes); meta.setTables(tables); MockConnection cx = jdbcMock.getMockConnection(); cx.setMetaData(meta); BasicSQLDialect dialect = mock(BasicSQLDialect.class); when(dialect.getDesiredTablesType()).thenReturn(new String[]{"TABLE"}); when(dialect.includeTable(anyString(), anyString(), any(Connection.class))).thenReturn(true); store.setSQLDialect(dialect); SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName("foo"); tb.setNamespaceURI("http://geotools.org"); tb.add("name", String.class); JDBCFeatureSource source = mock(JDBCFeatureSource.class); when(source.getDataStore()).thenReturn(store); MockResultSet rowData = new MockResultSet("foo"); rowData.addColumn("name", Arrays.asList("foo", "bar", "baz")); rowData.setStatement(new MockStatement(cx)); JDBCFeatureReader reader = new JDBCFeatureReader(rowData, cx, 0, source, tb.buildFeatureType(), new Query()); while (reader.hasNext()) { reader.next(); } verify(callback, times(1)).init(reader); verify(callback, times(4)).beforeNext(rowData); verify(callback, times(3)).afterNext(rowData, true); verify(callback, times(1)).afterNext(rowData, false); verify(callback, times(1)).finish(reader); } }