/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2014-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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureLock;
import org.geotools.data.Transaction;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.NameImpl;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import com.mockrunner.mock.jdbc.MockConnection;
import com.mockrunner.mock.jdbc.MockDataSource;
import com.mockrunner.mock.jdbc.MockDatabaseMetaData;
import com.mockrunner.mock.jdbc.MockPreparedStatement;
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;
/**
* Tests for GEOT-4792: optimizations for JDBCDataStore ensureAuthorization.
*
* @author Mauro Bartolomeoli (mauro.bartolomeoli @ geo-solutions.it)
*
*/
public class EnsureAuthorizationTest {
public static class TracingMockConnection extends MockConnection {
// number of calls to executeQuery
int calls = 0;
// flag set to true if a filter on FeatureId is done
boolean filteredOnIds = false;
MockStatement stmt;
MockPreparedStatement pstmt;
@Override
public Statement createStatement() throws SQLException {
// statement used for all queries
stmt = new MockStatement(this) {
@Override
public ResultSet executeQuery(String sql)
throws SQLException {
// check if the condition ID = 'some text' is present in the query
if(sql.matches("^.*\"ID\"\\s+=\\s+'.*'.*$")) {
filteredOnIds = true;
}
return new MockResultSet(SAMPLE_FEATURE_NAME);
}
};
calls++;
return stmt;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return prepareStatement(sql, 0, 0);
}
@Override
public PreparedStatement prepareStatement(final String sql, int arg1,
int arg2) throws SQLException {
pstmt = new MockPreparedStatement(this, sql, arg1, arg2) {
@Override
public ResultSet executeQuery()
throws SQLException {
// check if the condition ID = ? is present in the query
if(sql.matches("^.*\"ID\"\\s+=\\s+\\?.*$")) {
filteredOnIds = true;
}
return new MockResultSet(SAMPLE_FEATURE_NAME);
}
};
calls++;
return pstmt;
}
}
private JDBCDataStore dataStore;
private SimpleFeatureType featureType;
private Transaction tx;
private TracingMockConnection cx;
private static final String SAMPLE_FEATURE_NAME = "SAMPLE_FEATURE";
private static final String SAMPLE_FEATURE_ID = "SAMPLE_ID";
@Before
public void setUp() throws SQLException {
cx = new TracingMockConnection();
configureMetadata();
dataStore = new JDBCDataStore();
dataStore.setFilterFactory(CommonFactoryFinder.getFilterFactory2());
dataStore.setSQLDialect(createBasicSQLDialect());
MockDataSource dataSource = new MockDataSource();
dataSource.setupConnection(cx);
dataStore.setDataSource(dataSource);
featureType = Mockito.mock(SimpleFeatureType.class);
Mockito.when(featureType.getTypeName()).thenReturn(SAMPLE_FEATURE_NAME);
Mockito.when(featureType.getName()).thenReturn(new NameImpl(SAMPLE_FEATURE_NAME));
tx = new DefaultTransaction();
}
@Test
public void testFetchSizeUsedWhenNoPreparedStatement() throws IOException, SQLException {
dataStore.setFetchSize(1000);
createLock();
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertEquals(1000, cx.stmt.getFetchSize());
}
@Test
public void testQueryIsNotExecutedIfThereAreNoLocks() throws IOException, SQLException {
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertEquals(0, cx.calls);
}
@Test
public void testQueryIsExecutedIfThereAreLocks() throws IOException, SQLException {
createLock();
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertNotEquals(0, cx.calls);
}
@Test
public void testQueryIsFilteredOnLockedFeatureIds() throws IOException, SQLException {
createLock();
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertTrue(cx.filteredOnIds);
}
@Test
public void testQueryIsFilteredOnLockedFeatureIdsWithPreparedStatements() throws IOException, SQLException {
dataStore.setSQLDialect(createPreparedSQLDialect());
createLock();
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertTrue(cx.filteredOnIds);
}
@Test
public void testQueryIsNotFilteredOnLockedFeatureIdsIfThereAreTooManyLocks() throws IOException, SQLException {
createManyLocks();
dataStore.ensureAuthorization(featureType, Filter.INCLUDE, tx, cx);
assertFalse(cx.filteredOnIds);
}
private void createManyLocks() throws IOException {
for (int count = 0; count < JDBCDataStore.MAX_IDS_IN_FILTER + 1; count++) {
dataStore.getLockingManager().lockFeatureID(SAMPLE_FEATURE_NAME,
count + "", tx, new FeatureLock(count + "", 10000000L));
}
}
private void createLock() throws IOException {
dataStore.getLockingManager().lockFeatureID(SAMPLE_FEATURE_NAME,
SAMPLE_FEATURE_ID, tx,
new FeatureLock(SAMPLE_FEATURE_ID, 10000000L));
}
private void configureMetadata() throws SQLException {
((MockDatabaseMetaData)cx.getMetaData()).setSearchStringEscape("");
MockResultSet tableTypes = new MockResultSet("TABLE_TYPES");
tableTypes.addColumn("TABLE_TYPE");
tableTypes.addRow(new Object[] {"TABLE"});
((MockDatabaseMetaData)cx.getMetaData()).setTableTypes(tableTypes);
MockResultSet tables = new MockResultSet("TABLES");
tables.addColumn("TABLE_SCHEM");
tables.addColumn("TABLE_NAME");
tables.addRow(new Object[] {"", SAMPLE_FEATURE_NAME});
((MockDatabaseMetaData)cx.getMetaData()).setTables(tables);
MockResultSet key = new MockResultSet("KEY");
key.addColumn("COLUMN_NAME");
key.addColumn("DATA_TYPE");
key.addRow(new Object[] {"ID", 1});
((MockDatabaseMetaData)cx.getMetaData()).setPrimaryKeys(null, null, SAMPLE_FEATURE_NAME, key);
MockResultSet columns = new MockResultSet("COLUMNS");
columns.addColumn("COLUMN_NAME");
columns.addColumn("DATA_TYPE");
columns.addRow(new Object[] {"ID", 1});
((MockDatabaseMetaData)cx.getMetaData()).setColumns(null, null, SAMPLE_FEATURE_NAME, "ID", columns);
}
private BasicSQLDialect createBasicSQLDialect() {
return new BasicSQLDialect(dataStore) {
@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;
}
};
}
private PreparedStatementSQLDialect createPreparedSQLDialect() {
return new PreparedStatementSQLDialect(dataStore) {
@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;
}
@Override
public void setGeometryValue(Geometry g, int dimension, int srid,
Class binding, PreparedStatement ps, int column)
throws SQLException {
}
};
}
}