package eu.esdihumboldt.hale.io.jdbc.spatialite.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.sqlite.SQLiteConfig;
import com.google.common.io.ByteSink;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.vividsolutions.jts.geom.Geometry;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import eu.esdihumboldt.hale.common.core.io.impl.LogProgressIndicator;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
import eu.esdihumboldt.hale.common.core.io.supplier.FileIOSupplier;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.InstanceCollection;
import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.Schema;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.impl.DefaultSchemaSpace;
import eu.esdihumboldt.hale.io.jdbc.spatialite.reader.internal.SpatiaLiteInstanceReader;
import eu.esdihumboldt.hale.io.jdbc.spatialite.reader.internal.SpatiaLiteSchemaReader;
import eu.esdihumboldt.hale.io.jdbc.spatialite.writer.internal.SpatiaLiteInstanceWriter;
/**
* Class exposing methods with test logic, which are run by SpatiaLite test
* classes.
* <p>
* Subclasses should re-define the test parameters (stored in
* <code>protected</code> variables) to have the test suite run against a
* different dataset.
* </p>
*
* @author Stefano Costa, GeoSolutions
*/
public abstract class SpatiaLiteTestSuite {
private static final ALogger log = ALoggerFactory.getLogger(SpatiaLiteTestSuite.class);
/**
* File name of the source database.
*/
protected String SOURCE_DB_NAME;
/**
* Resource name of the source database.
*/
protected String SOURCE_DB_LOCATION;
/**
* Unqualified name of the only type stored in the source database.
*/
protected String SOUURCE_TYPE_LOCAL_NAME;
/**
* ID property name.
*/
protected String PROPERTY_ID_NAME;
/**
* ID of the only instance that will be checked by tests.
*/
protected Integer PROPERTY_ID_VALUE;
/**
* Property names of the source type.
*/
protected String[] SOUURCE_TYPE_PROPERTY_NAMES;
/**
* Property values of the only instance that will be checked by tests.
*/
protected Object[] SOUURCE_TYPE_PROPERTY_VALUES;
/**
* Number of instances in the source database.
*/
protected int SOURCE_INSTANCES_COUNT;
/**
* File name of the target database.
*/
protected String TARGET_DB_NAME;
private static Long RANDOM_NUMBER = null;
/**
* Copies the source database to a temporary file.
*
* @throws IOException if temp file can't be created
*/
public void createSourceTempFile() throws IOException {
ByteSource source = Resources.asByteSource(SpatiaLiteTestSuite.class
.getResource(SOURCE_DB_LOCATION));
ByteSink dest = Files.asByteSink(new File(getSourceTempFilePath()));
source.copyTo(dest);
}
/**
* Copies the target database to a temporary file and deletes all instances
* in it.
*
* @throws IOException if temp file can't be created or instances can't be
* deleted
*/
public void createTargetTempFile() throws IOException {
ByteSource source = Resources.asByteSource(SpatiaLiteTestSuite.class
.getResource(SOURCE_DB_LOCATION));
ByteSink dest = Files.asByteSink(new File(getTargetTempFilePath()));
source.copyTo(dest);
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:sqlite:" + getTargetTempFilePath());
Statement stmt = conn.createStatement();
stmt.executeUpdate("DELETE FROM " + SOUURCE_TYPE_LOCAL_NAME);
} catch (SQLException e) {
throw new IOException("Could not empty target DB", e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
/**
* Deletes the source temp file.
*/
public void deleteSourceTempFile() {
deleteTempFile(getSourceTempFilePath());
}
/**
* Deletes the target temp file.
*/
public void deleteTargetTempFile() {
deleteTempFile(getTargetTempFilePath());
}
/**
* Generates a random path (within the system's temporary folder) for the
* source database. The random number used to construct the path is saved in
* a static variable and thus the path will remain constant for the whole
* run.
*
* @return the absolute path of the source temp file
*/
public String getSourceTempFilePath() {
return getTempDir() + File.separator + getRandomNumber() + "_" + SOURCE_DB_NAME;
}
/**
* Generates a random path (within the system's temporary folder) for the
* target database. The random number used to construct the path is saved in
* a static variable and thus the path will remain constant for the whole
* run.
*
* @return the absolute path of the target temp file
*/
public String getTargetTempFilePath() {
return getTempDir() + File.separator + getRandomNumber() + "_" + TARGET_DB_NAME;
}
private String getTempDir() {
return System.getProperty("java.io.tmpdir");
}
private long getRandomNumber() {
if (RANDOM_NUMBER == null) {
RANDOM_NUMBER = System.currentTimeMillis();
}
return RANDOM_NUMBER;
}
private void deleteTempFile(String tempFilePath) {
File toBeDeleted = new File(tempFilePath);
if (toBeDeleted.exists()) {
toBeDeleted.delete();
}
}
/**
* Test - reads a sample SpatiaLite schema
*
* @throws Exception if an error occurs
*/
public void schemaReaderTest() throws Exception {
if (!isSpatiaLiteExtensionAvailable()) {
log.info("Skipping test because SpatiaLite extension is not available");
return;
}
Set<String> propertyNames = new HashSet<String>(Arrays.asList(SOUURCE_TYPE_PROPERTY_NAMES));
SpatiaLiteSchemaReader schemaReader = new SpatiaLiteSchemaReader();
schemaReader.setSource(new FileIOSupplier(new File(getSourceTempFilePath())));
IOReport report = schemaReader.execute(new LogProgressIndicator());
assertTrue(report.isSuccess());
Schema schema = schemaReader.getSchema();
assertEquals(1, schema.getMappingRelevantTypes().size());
TypeDefinition type = schema.getMappingRelevantTypes().iterator().next();
checkType(type, SOUURCE_TYPE_LOCAL_NAME, propertyNames);
}
/**
* Test - reads a sample SpatiaLite schema and data.
*
* @throws Exception if an error occurs
*/
public void instanceReaderTest() throws Exception {
if (!isSpatiaLiteExtensionAvailable()) {
log.info("Skipping test because SpatiaLite extension is not available");
return;
}
Map<String, Object> propertyMap = new HashMap<String, Object>();
for (int i = 0; i < SOUURCE_TYPE_PROPERTY_NAMES.length; i++) {
String key = SOUURCE_TYPE_PROPERTY_NAMES[i];
Object value = SOUURCE_TYPE_PROPERTY_VALUES[i];
propertyMap.put(key, value);
}
// ****** read Schema ******//
Schema schema = readSchema(getSourceTempFilePath());
assertNotNull(schema);
assertEquals(1, schema.getMappingRelevantTypes().size());
// Test properties
TypeDefinition schemaType = schema.getMappingRelevantTypes().iterator().next();
// Check every property for their existence
checkType(schemaType, SOUURCE_TYPE_LOCAL_NAME, propertyMap.keySet());
// ****** read Instances ******//
InstanceCollection instances = readInstances(schema, getSourceTempFilePath());
assertTrue(instances.hasSize());
assertEquals(SOURCE_INSTANCES_COUNT, instances.size());
checkInstances(instances, propertyMap);
}
/**
* Test - reads data from a source SpatiaLite database, writes them to a
* target SpatiaLite database and checks the results.
*
* @throws Exception if an error occurs
*/
public void instanceWriterTest() throws Exception {
if (!isSpatiaLiteExtensionAvailable()) {
log.info("Skipping test because SpatiaLite extension is not available");
return;
}
Map<String, Object> propertyMap = new HashMap<String, Object>();
for (int i = 0; i < SOUURCE_TYPE_PROPERTY_NAMES.length; i++) {
String key = SOUURCE_TYPE_PROPERTY_NAMES[i];
Object value = SOUURCE_TYPE_PROPERTY_VALUES[i];
propertyMap.put(key, value);
}
// ****** read Schema ******//
Schema schema = readSchema(getSourceTempFilePath());
assertNotNull(schema);
assertEquals(1, schema.getMappingRelevantTypes().size());
// Test properties
TypeDefinition schemaType = schema.getMappingRelevantTypes().iterator().next();
// Check every property for their existence
checkType(schemaType, SOUURCE_TYPE_LOCAL_NAME, propertyMap.keySet());
// ****** read Instances ******//
InstanceCollection instances = readInstances(schema, getSourceTempFilePath());
assertTrue(instances.hasSize());
assertEquals(SOURCE_INSTANCES_COUNT, instances.size());
checkInstances(instances, propertyMap);
// ****** write Instances ******//
// check target DB is empty
InstanceCollection targetInstances = readInstances(schema, getTargetTempFilePath());
assertTrue(targetInstances.hasSize());
assertEquals(0, targetInstances.size());
writeInstances(schema, getTargetTempFilePath(), instances);
// re-read instances to check they were written correctly
targetInstances = readInstances(schema, getTargetTempFilePath());
assertTrue(targetInstances.hasSize());
assertEquals(SOURCE_INSTANCES_COUNT, targetInstances.size());
checkInstances(targetInstances, propertyMap);
}
/**
* Checks whether the SpatiaLite extension is available on the system, by
* connecting to the source database and running the query:
* <p>
* {@code SELECT load_extension('mod_spatialite')}
* </p>
*
* @return true if the SpatiaLite extension could be loaded, false otherwise
*/
public boolean isSpatiaLiteExtensionAvailable() {
Connection conn = null;
try {
// enabling dynamic extension loading
// absolutely required by SpatiaLite
SQLiteConfig config = new SQLiteConfig();
config.enableLoadExtension(true);
String dbPath = getSourceTempFilePath();
if (!new File(dbPath).exists()) {
createSourceTempFile();
}
// create a database connection
conn = DriverManager.getConnection("jdbc:sqlite:" + dbPath, config.toProperties());
Statement stmt = conn.createStatement();
stmt.setQueryTimeout(30); // set timeout to 30 sec.
// loading SpatiaLite
stmt.execute("SELECT load_extension('mod_spatialite')");
} catch (Exception e) {
log.warn("Could not load SpatiaLite extension", e);
return false;
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// ignore
}
}
return true;
}
/**
* Reads a schema from a SpatiaLite database file.
*
* @param sourceFilePath the path to the source database file
* @return the schema
* @throws Exception any exception thrown by {@link SpatiaLiteSchemaReader}
*/
public Schema readSchema(String sourceFilePath) throws Exception {
SpatiaLiteSchemaReader schemaReader = new SpatiaLiteSchemaReader();
schemaReader.setSource(new FileIOSupplier(new File(sourceFilePath)));
IOReport report = schemaReader.execute(new LogProgressIndicator());
assertTrue(report.isSuccess());
return schemaReader.getSchema();
}
/**
* Reads instances from from a SpatiaLite database file with the provided
* schema.
*
* @param sourceSchema the schema of the source database
* @param sourceFilePath the path to the source database file
* @return the read instances
* @throws Exception any exception thrown by
* {@link SpatiaLiteInstanceReader}
*/
public InstanceCollection readInstances(Schema sourceSchema, String sourceFilePath)
throws Exception {
SpatiaLiteInstanceReader instanceReader = new SpatiaLiteInstanceReader();
instanceReader.setSource(new FileIOSupplier(new File(sourceFilePath)));
instanceReader.setSourceSchema(sourceSchema);
// Test instances
IOReport report = instanceReader.execute(new LogProgressIndicator());
assertTrue("Data import was not successfull.", report.isSuccess());
return instanceReader.getInstances();
}
/**
* Writes the provided instances to a SpatiaLite database.
*
* @param schema the target schema
* @param targetFilePath the path to the target database file
* @param instances the instances to write
* @throws Exception any exception thrown by
* {@link SpatiaLiteInstanceWriter}
*/
public void writeInstances(Schema schema, String targetFilePath, InstanceCollection instances)
throws Exception {
SpatiaLiteInstanceWriter instanceWriter = new SpatiaLiteInstanceWriter();
instanceWriter.setInstances(instances);
DefaultSchemaSpace ss = new DefaultSchemaSpace();
ss.addSchema(schema);
instanceWriter.setTargetSchema(ss);
instanceWriter.setTarget(new FileIOSupplier(new File(targetFilePath)));
// Test instances
IOReport report = instanceWriter.execute(new LogProgressIndicator());
assertTrue("Data export was not successfull.", report.isSuccess());
}
/**
* Checks the property definitions and values of the provided instances.
* Values will be checked for just one instance (the one with
* {@link #PROPERTY_ID_NAME} = {@link #PROPERTY_ID_VALUE}).
*
* @param instances the instances to check
* @param propertyMap the expected property names / values
*/
@SuppressWarnings("rawtypes")
public void checkInstances(InstanceCollection instances, Map<String, Object> propertyMap) {
// get type to check property definition
TypeDefinition type = instances.iterator().next().getDefinition();
checkType(type, SOUURCE_TYPE_LOCAL_NAME, propertyMap.keySet());
// Check the values of Instance with ID = 1
Instance instance = null;
Iterator<Instance> instanceIterator = instances.iterator();
while (instanceIterator.hasNext()) {
Instance currentInstance = instanceIterator.next();
Integer id = (Integer) currentInstance.getProperty(QName.valueOf(PROPERTY_ID_NAME))[0];
if (PROPERTY_ID_VALUE.equals(id)) {
instance = currentInstance;
break;
}
}
if (instance == null) {
fail(String.format("No instance found with %s = %s", PROPERTY_ID_NAME,
PROPERTY_ID_VALUE));
}
for (String propertyName : propertyMap.keySet()) {
@SuppressWarnings("null")
Object value = instance.getProperty(QName.valueOf(propertyName))[0];
if (value instanceof GeometryProperty) {
assertTrue(((Geometry) propertyMap.get(propertyName)).equalsExact(
((GeometryProperty) value).getGeometry(), 0.000001));
}
else {
assertEquals(propertyMap.get(propertyName), value);
}
}
}
/**
* Checks the name and the property definitions of the given type.
*
* @param type the type to check
* @param typeName the expected type name
* @param propertyNames the expected property names
*/
public void checkType(TypeDefinition type, String typeName, Set<String> propertyNames) {
assertNotNull(type);
assertEquals(typeName, type.getDisplayName());
for (ChildDefinition<?> child : type.getChildren()) {
assertTrue(propertyNames.contains(child.getName().getLocalPart()));
}
}
}