/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2007-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2007-2012, Geomatys
*
* 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.geotoolkit.internal.sql.table;
import java.util.Date;
import java.util.Locale;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import java.util.TimeZone;
import java.sql.SQLException;
import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.postgresql.ds.PGSimpleDataSource;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.geotoolkit.test.TestData;
import org.geotoolkit.test.image.ImageTestBase;
import org.geotoolkit.internal.io.Installation;
import org.junit.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
/**
* Base class for every tests requerying a connection to a coverage database.
* This test requires a connection to a PostgreSQL database. In addition, some
* test suite requires the test file to be present. See the following file for
* more information:
* <p>
* <a href="https://raw.githubusercontent.com/Geomatys/geotoolkit/master/modules/coverage/geotk-coverage-sql/src/test/resources/Tests/README.html">About large test files</a>
* <p>
* This class inherits {@link ImageTestBase} for allowing the display of images
* by the {@link #view(Coverage)} method if the {@link #viewEnabled} field is set
* to {@code true}. Most subclasses does not need this feature. However since
* coverages are the final purpose of {@code geotk-coverage-sql}, this functionality
* is provided here.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.09 (derived from Seagis)
*/
public abstract strictfp class CatalogTestBase extends ImageTestBase {
/**
* The connection to the database.
*/
private static SpatialDatabase database;
/**
* The {@code "rootDirectory"} property, or {@code null} if undefined.
*/
private static String rootDirectory;
/**
* Date parser, created when first needed.
*/
private transient DateFormat dateFormat;
/**
* For subclass constructors only.
*
* @param testing The class to be tested.
*/
protected CatalogTestBase(final Class<?> testing) {
super(testing);
}
/**
* Returns the file which should contain the configuration parameters.
* This method does not test is the file exist.
*/
private static Path getConfigurationFile() {
return Installation.TESTS.directory(true).resolve("coverage-sql.properties");
}
/**
* Returns {@code true} if the connection parameters are found,
* in which case the test is presumed executable.
*
* @return {@code true} if the test is presumed executable.
*/
protected static boolean canTest() {
return Files.isRegularFile(getConfigurationFile());
}
/**
* Creates the database when first needed.
*
* @return The database.
*/
protected static synchronized SpatialDatabase getDatabase() {
if (database == null) try {
final File pf = getConfigurationFile().toFile();
assumeTrue(pf.isFile()); // All tests will be skipped if the above resources is not found.
final Properties properties = TestData.readProperties(pf);
final PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setServerName(properties.getProperty("server"));
ds.setDatabaseName(properties.getProperty("database"));
/*
* Use reflection for instantiating the database, because it is defined
* as a package-privated class.
*/
if (true) {
final Class<? extends SpatialDatabase> databaseClass =
Class.forName("org.geotoolkit.coverage.sql.TableFactory").asSubclass(SpatialDatabase.class);
final Constructor<? extends SpatialDatabase> c = databaseClass.getConstructor(DataSource.class, Properties.class);
c.setAccessible(true);
database = c.newInstance(ds, properties);
} else {
database = new SpatialDatabase(ds, properties);
}
rootDirectory = properties.getProperty(ConfigurationKey.ROOT_DIRECTORY.key);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new AssertionError(e); // This will cause a JUnit failure.
}
return database;
}
/**
* Closes the connection to the database.
*
* @throws SQLException If an error occurred while closing the connection.
*/
@AfterClass
public static synchronized void shutdown() throws SQLException {
final Database db = database;
database = null;
if (db != null) {
db.reset();
}
}
/**
* Subclasses shall invoke this method if the remaining code in a method requires
* the image files. Typically, this method is invoked right before the first call
* to {@link org.geotoolkit.coverage.sql.GridCoverageEntry#read}.
*
* @since 3.10
*/
protected static synchronized void requireImageData() {
assertNotNull("This method can be invoked only after getDatabase().", database);
assumeNotNull(rootDirectory);
}
/**
* Returns the path to the given image. This method can be invoked only if
* {@link #requireImageData()} has been successfully invoked.
*
* @param path The path to the image, relative to the data root directory.
* @return The absolute path to the image.
*
* @since 3.12
*/
protected static synchronized File toImageFile(final String path) {
assertNotNull("requireImageData() must be invoked first.", rootDirectory);
final File file = new File(rootDirectory, path);
assertTrue("Not a file: " + file, file.isFile());
return file;
}
/**
* Asserts that the two given objects are approximatively equal.
* See {@link ComparisonMode#APPROXIMATIVE} for more information.
*
* @param expected The expected object.
* @param actual The actual object.
*
* @since 3.20
*/
public static void assertEqualsApproximatively(final Object expected, final Object actual) {
assertTrue(Utilities.deepEquals(expected, actual, ComparisonMode.DEBUG));
assertTrue(Utilities.deepEquals(expected, actual, ComparisonMode.APPROXIMATIVE));
}
/**
* Returns the date format.
*/
private DateFormat getDateFormat() {
DateFormat df = dateFormat;
if (df == null) {
dateFormat = df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
df.setLenient(false);
}
return df;
}
/**
* Parses the date for the given string using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
* in UTC timezone.
*
* @param date The date as a {@link String}.
* @return The date as a {@link Date}.
*
* @since 3.15
*/
protected final synchronized Date date(final String date) {
assertNotNull("A date must be specified", date);
final DateFormat dateFormat = getDateFormat();
try {
return dateFormat.parse(date);
} catch (ParseException e) {
throw new AssertionError(e);
}
}
}