This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 18e2964bf2afd0b03a178b8973bd0d545e013a3c Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Jul 12 15:18:05 2023 +0200 `ScriptRunner.run(…)` shall receive an `InputStream` supplied by the caller instead of invoking `Class.getResourceAsStream(String)` as a convenience. This is necessary in JPMS (Jigsaw) context. --- .../sis/internal/metadata/sql/ScriptRunner.java | 35 +++++++++++++++++----- .../org/apache/sis/metadata/sql/Installer.java | 15 ++++++---- .../java/org/apache/sis/test/sql/TestDatabase.java | 29 +++++++++--------- .../sql/feature/SelectionClauseWriterTest.java | 5 ++-- .../sis/internal/sql/postgis/PostgresTest.java | 28 +++++++++++++++-- .../org/apache/sis/storage/sql/SQLStoreTest.java | 29 +++++++++++++----- 6 files changed, 101 insertions(+), 40 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java index 5ef740b0c8..ee703a1350 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java @@ -22,12 +22,15 @@ import java.util.Locale; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.function.BiFunction; +import java.io.FileNotFoundException; import java.io.EOFException; import java.io.IOException; import java.io.BufferedReader; import java.io.LineNumberReader; import java.io.InputStreamReader; +import java.io.InputStream; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.sql.Statement; import java.sql.Connection; import java.sql.SQLException; @@ -51,7 +54,7 @@ import org.apache.sis.util.resources.Errors; * * @author Martin Desruisseaux (Geomatys) * @author Johann Sorel (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.7 */ public class ScriptRunner implements AutoCloseable { @@ -440,24 +443,40 @@ public class ScriptRunner implements AutoCloseable { } /** - * Runs the SQL script of the given name in the same package than the given class. - * The script is presumed encoded in UTF-8. + * Runs the SQL script from the given (filename, input stream) pair. + * The file name is used only if an error needs to be reported. + * The stream content is presumed encoded in UTF-8 and the stream will be closed by this method. + * This method is intended to be invoked by code like this: * - * @param loader the class to use for loading the SQL script. - * @param filename the SQL script filename, relative to the {@code loader} package. + * {@snippet lang="java" : + * run("myFile.sql", MyClass.getResourceAsStream("myFile.sql")); + * } + * + * <h4>Rational</h4> + * Because {@link Class#getResourceAsStream(String)} is caller-sensitive, it must be invoked + * from the module containing the resource. Invoking {@code getResourceAsStream(…)} from this + * {@code run(…)} method does not work even with a {@link Class} instance passed in argument. + * + * @param filename name of the SQL script being executed. This is used only for error reporting. + * @param in the stream to read. It will be closed by this method. * @return the number of rows added or modified as a result of the statement execution. * @throws IOException if an error occurred while reading the input. * @throws SQLException if an error occurred while executing a SQL statement. */ - public final int run(final Class<?> loader, final String filename) throws IOException, SQLException { - try (BufferedReader in = new LineNumberReader(new InputStreamReader(loader.getResourceAsStream(filename), "UTF-8"))) { - return run(filename, in); + public final int run(final String filename, final InputStream in) throws IOException, SQLException { + if (in == null) { + throw new FileNotFoundException(Errors.format(Errors.Keys.FileNotFound_1, filename)); + } + try (BufferedReader reader = new LineNumberReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + return run(filename, reader); } } /** * Runs the script from the given reader. Lines are read and grouped up to the * terminal {@value #END_OF_STATEMENT} character, then sent to the database. + * Note that contrarily to {@link #run(String, InputStream)}, + * this method does <strong>not</strong> close the given reader. * * @param filename name of the SQL script being executed. This is used only for error reporting. * @param in the stream to read. It is caller's responsibility to close this reader. diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java index ee3831d080..9c0140c26b 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java @@ -34,7 +34,7 @@ import org.apache.sis.util.StringBuilders; * dependencies. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.4 * @since 0.8 */ final class Installer extends ScriptRunner { @@ -71,10 +71,15 @@ final class Installer extends ScriptRunner { * Runs the installation scripts. */ public void run() throws IOException, SQLException { - run(Installer.class, "Citations.sql"); - run(Installer.class, "Contents.sql"); - run(Installer.class, "Metadata.sql"); - run(Installer.class, "Referencing.sql"); + final String[] scripts = { + "Citations.sql", + "Contents.sql", + "Metadata.sql", + "Referencing.sql" + }; + for (final String filename : scripts) { + run(filename, Installer.class.getResourceAsStream(filename)); + } } /** diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/sql/TestDatabase.java b/core/sis-metadata/src/test/java/org/apache/sis/test/sql/TestDatabase.java index a76ca252f6..d5bdcad545 100644 --- a/core/sis-metadata/src/test/java/org/apache/sis/test/sql/TestDatabase.java +++ b/core/sis-metadata/src/test/java/org/apache/sis/test/sql/TestDatabase.java @@ -16,7 +16,10 @@ */ package org.apache.sis.test.sql; +import java.util.List; +import java.util.function.Supplier; import java.io.IOException; +import java.io.InputStream; import javax.sql.DataSource; import java.sql.Connection; import java.sql.Statement; @@ -276,25 +279,23 @@ public class TestDatabase implements AutoCloseable { } /** - * Executes the given SQL statements, or statements from the given resource files. - * If an element from the {@code scripts} array begin by {@code "file:"}, then the part - * after {@code ":"} will be read as a resource file loaded by the given {@code loader}. - * Otherwise the script is executed as a SQL statement. Null element are ignored. + * Executes the given SQL statements, or statements from the given resource streams. + * Each element of the list shall be either a {@link String} or a {@code Supplier<InputStream>}. + * In the latter case, {@code Supplier.toString()} is used in error message if an error happens. * - * @param loader a class in the package of the resource file. This is usually the test class. - * @param scripts SQL statements or names of the SQL files to load and execute. + * @param scripts SQL statements or names of the SQL files to load and execute. * @throws IOException if an error occurred while reading a resource file. * @throws SQLException if an error occurred while executing a SQL statement. + * @throws ClassCastException if an element of the list is not a {@code String} or a {@code Supplier<InputStream>}. */ - public void executeSQL(final Class<?> loader, final String... scripts) throws IOException, SQLException { + public final void executeSQL(final List<?> scripts) throws IOException, SQLException { try (Connection c = source.getConnection(); ScriptRunner r = new ScriptRunner(c, 1000)) { - for (final String sql : scripts) { - if (sql != null) { - if (sql.startsWith("file:")) { - r.run(loader, sql.substring(5)); - } else { - r.run(sql); - } + for (final Object sql : scripts) { + if (sql instanceof String) { + r.run((String) sql); + } else { + final var s = (Supplier<?>) sql; + r.run(s.toString(), (InputStream) s.get()); } } } diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java index d89eb696de..b38b6c0ad8 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java @@ -16,6 +16,7 @@ */ package org.apache.sis.internal.sql.feature; +import java.util.List; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.DataStore; @@ -73,9 +74,7 @@ public final class SelectionClauseWriterTest extends TestCase implements SchemaM @Test public void testOnDerby() throws Exception { try (TestDatabase db = TestDatabase.create("SQLStore")) { - db.executeSQL(SelectionClauseWriterTest.class, - "CREATE TABLE TEST (ALPHA INTEGER, BETA INTEGER, GAMMA INTEGER, PI FLOAT);"); - + db.executeSQL(List.of("CREATE TABLE TEST (ALPHA INTEGER, BETA INTEGER, GAMMA INTEGER, PI FLOAT);")); final StorageConnector connector = new StorageConnector(db.source); connector.setOption(SchemaModifier.OPTION, this); try (DataStore store = new SQLStoreProvider().open(connector)) { diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java index de621ddf2e..1a114b6ad3 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java @@ -16,14 +16,17 @@ */ package org.apache.sis.internal.sql.postgis; +import java.util.List; +import java.util.stream.Stream; +import java.util.function.Supplier; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.lang.reflect.Method; -import java.util.stream.Stream; import org.opengis.geometry.Envelope; import org.opengis.util.FactoryException; import org.opengis.referencing.crs.ProjectedCRS; @@ -65,11 +68,30 @@ import static org.opengis.test.Assert.assertInstanceOf; * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.1 */ @DependsOn({RasterReaderTest.class, RasterWriterTest.class}) public final class PostgresTest extends TestCase { + /** + * Creates a new test case. + */ + public PostgresTest() { + } + + /** + * Provides a stream for a resource in the same package than this class. + * The implementation invokes {@code getResourceAsStream(filename)}. + * This invocation must be done in this module because the invoked + * method is caller-sensitive. + */ + private static Supplier<InputStream> resource(final String filename) { + return new Supplier<>() { + @Override public String toString() {return filename;} + @Override public InputStream get() {return PostgresTest.class.getResourceAsStream(filename);} + }; + } + /** * Tests {@link Postgres#parseVersion(String)}. */ @@ -89,7 +111,7 @@ public final class PostgresTest extends TestCase { @Test public void testSpatialFeatures() throws Exception { try (TestDatabase database = TestDatabase.createOnPostgreSQL(SQLStoreTest.SCHEMA, true)) { - database.executeSQL(PostgresTest.class, "file:SpatialFeatures.sql"); + database.executeSQL(List.of(resource("SpatialFeatures.sql"))); final StorageConnector connector = new StorageConnector(database.source); connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.JTS); final ResourceDefinition table = ResourceDefinition.table(null, SQLStoreTest.SCHEMA, "SpatialData"); diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java index 53bb6abf66..94a763cbce 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java @@ -20,9 +20,12 @@ import java.util.Map; import java.util.HashMap; import java.util.HashSet; import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.stream.Stream; +import java.util.function.Supplier; import java.util.concurrent.atomic.AtomicInteger; +import java.io.InputStream; import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.FeatureQuery; import org.apache.sis.storage.StorageConnector; @@ -108,6 +111,19 @@ public final class SQLStoreTest extends TestOnAllDatabases { FF = DefaultFilterFactory.forFeatures(); } + /** + * Provides a stream for a resource in the same package than this class. + * The implementation invokes {@code getResourceAsStream(filename)}. + * This invocation must be done in this module because the invoked + * method is caller-sensitive. + */ + private static Supplier<InputStream> resource(final String filename) { + return new Supplier<>() { + @Override public String toString() {return filename;} + @Override public InputStream get() {return SQLStoreTest.class.getResourceAsStream(filename);} + }; + } + /** * Runs all tests on a single database software. A temporary schema is created at the beginning of this method * and deleted after all tests finished. The schema is created and populated by the {@code Features.sql} script. @@ -118,14 +134,13 @@ public final class SQLStoreTest extends TestOnAllDatabases { */ @Override protected void test(final TestDatabase database, final boolean noschema) throws Exception { - final String[] scripts = { - "CREATE SCHEMA " + SCHEMA + ';', - "file:Features.sql" - }; - if (!noschema) { - scripts[0] = null; // Omit the "CREATE SCHEMA" statement if the schema already exists. + final var scripts = new ArrayList<>(2); + if (noschema) { + scripts.add("CREATE SCHEMA " + SCHEMA + ';'); + // Omit the "CREATE SCHEMA" statement if the schema already exists. } - database.executeSQL(SQLStoreTest.class, scripts); + scripts.add(resource("Features.sql")); + database.executeSQL(scripts); final StorageConnector connector = new StorageConnector(database.source); final ResourceDefinition table = ResourceDefinition.table(null, noschema ? null : SCHEMA, "Cities"); testTableQuery(connector, table);