/**
* This program 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, either version 3 of the License, or
* (at your option) any later version.
* <p>
* This program 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 General Public License for more details.
* <p>
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Nuno Oliveira, GeoSolutions S.A.S., Copyright 2016
*/
package org.geowebcache.sqlite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.storage.StorageException;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.geowebcache.sqlite.Utils.Tuple;
import static org.geowebcache.sqlite.Utils.Tuple.tuple;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
public final class SqliteConnectionManagerTest extends TestSupport {
private static Log LOGGER = LogFactory.getLog(SqliteConnectionManagerTest.class);
private List<SqliteConnectionManager> connectionManagersToClean;
@Before
public void beforeTest() throws Exception {
super.beforeTest();
connectionManagersToClean = new ArrayList<>();
}
@After
public void afterTest() throws Exception {
for (SqliteConnectionManager connectionManager : connectionManagersToClean) {
try {
connectionManager.reapAllConnections();
connectionManager.stopPoolReaper();
} catch (Exception exception) {
// nothing that we can do, so just ignoring this exception
}
}
super.afterTest();
}
@Test
public void testGetConnection() throws StorageException {
SqliteConnectionManager connectionManager = new SqliteConnectionManager(Integer.MAX_VALUE, 1000);
connectionManagersToClean.add(connectionManager);
connectionManager.doWork(buildRootFile("tiles", "data_base.sqlite"), true, connection -> {
insertInTestTable(connection, "name", "europe");
});
connectionManager.reapAllConnections();
assertThat(connectionManager.getPool().size(), is(0));
connectionManager.doWork(buildRootFile("tiles", "data_base.sqlite"), true, connection -> {
String value = getFromTestTable(connection, "name");
assertThat(value, notNullValue());
assertThat(value, is("europe"));
closeConnectionQuietly(connection);
});
}
@Test
@Ignore
public void testMultiThreadsWithSingleFile() throws Exception {
genericMultiThreadsTest(10, 500, Integer.MAX_VALUE, buildRootFile("data_base_a.sqlite"));
}
@Test
@Ignore
public void testMultiThreadsWithMultipleFiles() throws Exception {
genericMultiThreadsTest(10, 500, 10,
buildRootFile("data_base_a.sqlite"),
buildRootFile("data_base_b.sqlite"),
buildRootFile("data_base_c.sqlite"),
buildRootFile("data_base_d.sqlite"),
buildRootFile("data_base_e.sqlite")
);
}
@Test
@Ignore
public void testMultiThreadsWithMultipleFilesWithCacheLimit() throws Exception {
genericMultiThreadsTest(10, 500, 1,
buildRootFile("data_base_a.sqlite"),
buildRootFile("data_base_b.sqlite"),
buildRootFile("data_base_c.sqlite"),
buildRootFile("data_base_d.sqlite"),
buildRootFile("data_base_e.sqlite")
);
}
@Test
@Ignore
public void testReplaceOperation() throws Exception {
SqliteConnectionManager connectionManager = new SqliteConnectionManager(Integer.MAX_VALUE, 1000);
connectionManagersToClean.add(connectionManager);
File file1 = buildRootFile("tiles", "data_base_1.sqlite");
Utils.createFileParents(file1);
File file2 = buildRootFile("tiles", "data_base_2.sqlite");
Utils.createFileParents(file2);
connectionManager.doWork(file1, false, connection -> {
insertInTestTable(connection, "name", "europe");
closeConnectionQuietly(connection);
});
file2.createNewFile();
connectionManager.replace(file1, file2);
connectionManager.doWork(file1, false, connection -> {
createTestTable(connection);
String value = getFromTestTable(connection, "name");
assertThat(value, nullValue());
closeConnectionQuietly(connection);
});
}
private void genericMultiThreadsTest(int threadsNumber, int workersNumber,
long poolSize, File... files) throws Exception {
SqliteConnectionManager connectionManager = new SqliteConnectionManager(poolSize, 10);
connectionManagersToClean.add(connectionManager);
ExecutorService executor = Executors.newFixedThreadPool(threadsNumber);
Random random = new Random();
List<Future<Tuple<File, String>>> results = new ArrayList<>();
for (int i = 0; i < workersNumber; i++) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Submitted worker '%d'\\'%d'.", i, workersNumber));
}
executor.submit(() -> {
File file = files[random.nextInt(files.length)];
String key = UUID.randomUUID().toString();
return connectionManager.doWork(file, false, connection -> {
insertInTestTable(connection, key, "value-" + key);
closeConnectionQuietly(connection);
return tuple(file, key);
});
});
}
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
connectionManager.reapAllConnections();
assertThat(connectionManager.getPool().size(), is(0));
for (Future<Tuple<File, String>> result : results) {
File file = result.get().first;
String key = result.get().second;
connectionManager.doWork(file, true, connection -> {
String value = getFromTestTable(connection, key);
assertThat(value, notNullValue());
assertThat(value, is("value-" + key));
closeConnectionQuietly(connection);
});
}
}
private static void closeConnectionQuietly(Connection connection) {
try {
connection.close();
} catch (Exception exception) {
throw Utils.exception(exception, "Error closing connection.");
}
}
private static void createTestTable(Connection connection) {
execute(connection, "CREATE TABLE IF NOT EXISTS test " +
"(key text, value text, CONSTRAINT pk_metadata PRIMARY KEY(key));");
}
private static void insertInTestTable(Connection connection, String key, String value) {
createTestTable(connection);
execute(connection, "INSERT INTO test VALUES ('%s', '%s');", key, value);
}
private static String getFromTestTable(Connection connection, String key) {
return new ExecuteQuery(connection, "SELECT value FROM test WHERE key = '%s' ORDER BY key;", key) {
String result;
@Override
public void extract(ResultSet resultSet) throws Exception {
if (resultSet.next()) {
result = resultSet.getString(1);
}
}
}.result;
}
private static void execute(Connection connection, String sql, Object... arguments) {
String finalSql = String.format(sql, arguments);
try (PreparedStatement statement = connection.prepareStatement(finalSql)) {
statement.execute();
} catch (Exception exception) {
throw Utils.exception(exception, "Error executing SQL '%s'.", finalSql);
}
}
private static abstract class ExecuteQuery {
public abstract void extract(ResultSet resultSet) throws Exception;
public ExecuteQuery(Connection connection, String query, Object... arguments) {
String finalQuery = String.format(query, arguments);
try (PreparedStatement statement = connection.prepareStatement(finalQuery)) {
extract(statement.executeQuery());
} catch (Exception exception) {
throw Utils.exception(exception, "Error executing query '%s'.", finalQuery);
}
}
}
}