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 9703e401f8aeff168bcc0e04eca1757bb0886153 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Aug 20 16:27:09 2025 +0200 Fix the generation of embedded Derby database containing EPSG data. If the instruction documented in `.../epsg/README.md` are followed, then a build with tests enabled will automatically create the full spatial metadata database ready to deploy. --- optional/build.gradle.kts | 18 ++-- .../sis/resources/embedded/EmbeddedResources.java | 14 +-- .../sis/resources/embedded/package-info.java | 12 +-- .../resources/embedded/EmbeddedResourcesTest.java | 40 ++++--- .../apache/sis/resources/embedded/Generator.java | 117 +++++++++++---------- .../sis/referencing/factory/sql/epsg/README.md | 13 +-- .../factory/sql/epsg/ScriptProviderTest.java | 4 +- 7 files changed, 112 insertions(+), 106 deletions(-) diff --git a/optional/build.gradle.kts b/optional/build.gradle.kts index 51e4111a76..cdc3818db3 100644 --- a/optional/build.gradle.kts +++ b/optional/build.gradle.kts @@ -175,13 +175,17 @@ fun downloadFontGIS() { * Adds symbolic links to EPSG license if those optional data are present. */ fun addLicenseEPSG() { - var targetFile = File(file("build"), "classes/java/main/org.apache.sis.referencing.epsg/META-INF/LICENSE") - if (!targetFile.exists()) { - val sourceFile = File(file("src"), "org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/LICENSE.txt") - if (sourceFile.exists()) { - Files.createLink(targetFile.toPath(), sourceFile.toPath()) - targetFile = File(file("build"), "classes/java/main/org.apache.sis.referencing.database/META-INF/LICENSE") - Files.createLink(targetFile.toPath(), sourceFile.toPath()) + var buildDir = file("build") + if (buildDir.exists()) { + var targetFile = File(buildDir, "classes/java/main/org.apache.sis.referencing.epsg/META-INF/LICENSE") + if (!targetFile.exists()) { + val sourceFile = File(file("src"), "org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/LICENSE.txt") + if (sourceFile.exists()) { + var realPath = sourceFile.toPath().toRealPath() + Files.createLink(targetFile.toPath(), realPath) + targetFile = File(file("build"), "classes/java/main/org.apache.sis.referencing.database/META-INF/LICENSE") + Files.createLink(targetFile.toPath(), realPath) + } } } } diff --git a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java index c53faee85b..a97598cfac 100644 --- a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java +++ b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java @@ -18,6 +18,7 @@ package org.apache.sis.resources.embedded; import java.util.Set; import java.util.Locale; +import java.util.StringJoiner; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; @@ -30,7 +31,7 @@ import org.apache.sis.util.resources.Errors; /** - * Provides an embedded database for the EPSG geodetic dataset and other resources. + * Provides an embedded database for the <abbr>EPSG</abbr> geodetic dataset and other resources. * Provides also a copy of the <a href="https://epsg.org/terms-of-use.html">EPSG terms of use</a>, * which should be accepted by users before the EPSG dataset can be installed. * @@ -45,7 +46,7 @@ public class EmbeddedResources extends InstallationResources { * The name of the database embedded in the JAR file. * It must be an invalid package name, because otherwise the Java Platform Module System (JPMS) enforces * encapsulation in the same way as non-exported packages, which makes the database inaccessible to Derby. - * This naming trick is part of JPMS specification, so it should be reliable. + * This naming trick is part of <abbr>JPMS</abbr> specification, so it should be reliable. */ static final String EMBEDDED_DATABASE = "spatial-metadata"; @@ -95,14 +96,13 @@ public class EmbeddedResources extends InstallationResources { } else { return null; } - final StringBuilder buffer = new StringBuilder(); - final String lineSeparator = System.lineSeparator(); + final var buffer = new StringJoiner(System.lineSeparator(), "", System.lineSeparator()); try (BufferedReader in = new BufferedReader(new InputStreamReader( EmbeddedResources.class.getResourceAsStream(filename), "UTF-8"))) { String line; while ((line = in.readLine()) != null) { - buffer.append(line).append(lineSeparator); + buffer.add(line); } } return buffer.toString(); @@ -130,14 +130,14 @@ public class EmbeddedResources extends InstallationResources { @Override public DataSource getResource(String authority, int index) { verifyAuthority(authority); - final EmbeddedDataSource ds = new EmbeddedDataSource(); + final var ds = new EmbeddedDataSource(); ds.setDataSourceName(Initializer.DATABASE); ds.setDatabaseName("classpath:SIS_DATA/Databases/" + EMBEDDED_DATABASE); return ds; } /** - * Unconditionally throws an exception since the embedded database is not provided as SQL scripts. + * Unconditionally throws an exception since the embedded database is not provided as <abbr>SQL</abbr> scripts. * * @param authority shall be {@code "Embedded"}. * @param resource shall be 0. diff --git a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/package-info.java b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/package-info.java index 2ccb718065..893d4b032d 100644 --- a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/package-info.java +++ b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/package-info.java @@ -16,15 +16,15 @@ */ /** - * Provides non-free data, including the EPSG geodetic dataset, in a single read-only JAR file. - * This module contains a copy of EPSG geodetic dataset in an embedded Apache Derby database. + * Provides data, including the non-free <abbr>EPSG</abbr> geodetic dataset, in a single read-only <abbr>JAR</abbr> file. + * This module contains a copy of the <abbr>EPSG</abbr> geodetic dataset in an embedded Apache Derby database. * Having this module on the module-path avoid the need to set the {@code SIS_DATA} environment variable - * for using the Coordinate Reference Systems (<abbr>CRS</abbr>) and Coordinate Operations defined by EPSG. + * for using the Coordinate Reference Systems (<abbr>CRS</abbr>) and Coordinate Operations defined by <abbr>EPSG</abbr>. * * <h2>Licensing</h2> - * EPSG is maintained by the <a href="https://www.iogp.org/">International Association of Oil and Gas Producers</a> - * (IOGP) Surveying & Positioning Committee and is subject to <a href="https://epsg.org/terms-of-use.html">EPSG - * terms of use</a>. This module is not included in the Apache <abbr>SIS</abbr> distribution of convenience binaries, + * <abbr>EPSG</abbr> is maintained by the <a href="https://www.iogp.org/">International Association of Oil and Gas Producers</a> (<abbr>IOGP</abbr>) + * Surveying & Positioning Committee and is subject to <a href="https://epsg.org/terms-of-use.html"><abbr>EPSG</abbr> terms of use</a>. + * This module is not included in the Apache <abbr>SIS</abbr> distribution of convenience binaries, * and the source code contains only the Java classes without the <abbr>EPSG</abbr> data. For use in an application, * see <a href="https://sis.apache.org/epsg.html">How to use EPSG geodetic dataset</a> on the <abbr>SIS</abbr> web site. * diff --git a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java index 6dee0b4fbb..4c8393cdcc 100644 --- a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java +++ b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java @@ -27,10 +27,10 @@ import org.apache.sis.setup.InstallationResources; import org.apache.sis.metadata.sql.privy.Initializer; import org.apache.sis.system.DataDirectory; import org.apache.sis.referencing.CRS; -import org.apache.sis.referencing.factory.sql.epsg.ScriptProvider; // Test dependencies import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.apache.sis.test.TestUtilities; @@ -41,27 +41,29 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * Tests {@link EmbeddedResources}. + * This test has the side-effect of creating the database if it does not already exists. * * @author Martin Desruisseaux (Geomatys) */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public final strictfp class EmbeddedResourcesTest { - /** - * Whether the database has been created. - */ - private static boolean databaseCreated; - /** * Creates a new test case. + * + * @throws Exception if an error occurred while creating the database. */ - public EmbeddedResourcesTest() { + public EmbeddedResourcesTest() throws Exception { + new Generator().createIfAbsent(); } /** - * Skips the test if the EPSG scripts are not present. + * Skips the test if the <abbr>EPSG</abbr> scripts are not present. * This method uses {@code LICENSE.txt} as a sentinel file. + * Note that even if the <abbr>EPSG</abbr> data are not available, + * the database may still contain other metadata. */ - private static void assumeDataPresent() { - assumeTrue(ScriptProvider.class.getResource("LICENSE.txt") != null, + private static void assumeContainsEPSG() { + assumeTrue(EmbeddedResources.class.getResource("LICENSE.txt") != null, "EPSG resources not found. See `README.md` for manual installation."); } @@ -69,13 +71,7 @@ public final strictfp class EmbeddedResourcesTest { * Returns the {@link EmbeddedResources} instance declared in the {@code META-INF/services/} directory. * The provider may coexist with providers defined in other modules, so we need to filter them. */ - private static synchronized InstallationResources getInstance() { - if (!databaseCreated) try { - new Generator().run(); - databaseCreated = true; - } catch (Exception e) { - throw new AssertionError(e); - } + private static InstallationResources getInstance() { InstallationResources provider = null; for (InstallationResources candidate : ServiceLoader.load(InstallationResources.class)) { if (candidate instanceof EmbeddedResources) { @@ -94,7 +90,7 @@ public final strictfp class EmbeddedResourcesTest { */ @Test public void testLicences() throws IOException { - assumeDataPresent(); + assumeContainsEPSG(); final InstallationResources provider = getInstance(); assertTrue(provider.getLicense("Embedded", null, "text/plain").contains("IOGP")); assertTrue(provider.getLicense("Embedded", null, "text/html" ).contains("IOGP")); @@ -107,13 +103,13 @@ public final strictfp class EmbeddedResourcesTest { */ @Test public void testConnection() throws Exception { - assumeDataPresent(); + assumeContainsEPSG(); final String dir = DataDirectory.getenv(); - assertTrue((dir == null) || dir.isEmpty(), "The SIS_DATA environment variable must be unset for enabling this test."); + assumeTrue((dir == null) || dir.isEmpty(), "The SIS_DATA environment variable must be unset for enabling this test."); final DataSource ds = Initializer.getDataSource(); assertNotNull(ds, "Cannot find the data source."); try (Connection c = ds.getConnection()) { - assertEquals("jdbc:derby:classpath:SIS_DATA/Databases/spatial-metadata", c.getMetaData().getURL(), "URL"); + assertEquals("jdbc:derby:classpath:SIS_DATA/Databases/" + EmbeddedResources.EMBEDDED_DATABASE, c.getMetaData().getURL(), "URL"); try (Statement s = c.createStatement()) { try (ResultSet r = s.executeQuery("SELECT COORD_REF_SYS_NAME FROM EPSG.\"Coordinate Reference System\" WHERE COORD_REF_SYS_CODE = 4326")) { assertTrue(r.next(), "ResultSet.next()"); @@ -133,7 +129,7 @@ public final strictfp class EmbeddedResourcesTest { */ @Test public void testCrsforCode() throws FactoryException { - assumeDataPresent(); + assumeContainsEPSG(); CoordinateReferenceSystem crs = CRS.forCode("EPSG:6676"); String area = TestUtilities.getSingleton(crs.getDomains()).getDomainOfValidity().getDescription().toString(); assertTrue(area.contains("Japan"), area); diff --git a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java index fcc3b69f99..95b646aaa1 100644 --- a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java +++ b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java @@ -16,16 +16,11 @@ */ package org.apache.sis.resources.embedded; -import java.util.List; import java.util.ArrayList; -import java.util.Map; import java.util.HashMap; -import java.io.InputStream; import java.io.IOException; -import java.io.FileNotFoundException; import java.lang.reflect.Method; import java.net.URISyntaxException; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.sql.CallableStatement; @@ -41,15 +36,14 @@ import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.metadata.sql.privy.Initializer; import org.apache.sis.metadata.sql.privy.LocalDataSource; import org.apache.sis.system.DataDirectory; -import org.apache.sis.system.Shutdown; import org.apache.sis.referencing.factory.sql.EPSGFactory; import org.apache.sis.referencing.factory.sql.epsg.ScriptProvider; /** - * Generates {@code SpatialMetadata} database with EPSG geodetic dataset. - * This class is invoked only at build time and should be excluded from the final JAR file. - * The {@link #main(String[])} method generates resources directly in the {@code target/classes} directory. + * Generates {@code SpatialMetadata} database with <abbr>EPSG</abbr> geodetic dataset. + * This class is invoked only at build time and should be excluded from the final <abbr>JAR</abbr> file. + * The {@link #createIfAbsent()} method generates resources directly in the {@code target/classes} directory. * * <p><b>Note:</b> * Maven usage is to generate resources in the {@code target/generated-resources} directory. @@ -58,39 +52,22 @@ import org.apache.sis.referencing.factory.sql.epsg.ScriptProvider; * * @author Martin Desruisseaux (Geomatys) */ -public final class Generator extends ScriptProvider { +final class Generator extends ScriptProvider { /** - * Generates the embedded resources in the {@code target/classes} directory. - * See class Javadoc for more information. - * - * @param args ignored. Can be null. - * @throws Exception if a failure occurred while searching directories, - * executing SQL scripts, copying data or any other operation. + * Directory where the {@link EmbeddedResources} class file is located. + * This is the directory where to copy the license files. */ - public static void main(String[] args) throws Exception { - new Generator().run(); - Shutdown.stop(Generator.class); - } + private final Path classesDirectory; /** - * Generates the embedded resources in the {@code target/classes} directory. - * - * @throws Exception if a failure occurred while searching directories, - * executing SQL scripts, copying data or any other operation. + * Directory where to search for <abbr>EPSG</abbr> data. */ - final void run() throws Exception { - if (dataSource != null) { - createMetadata(); - createEPSG(); - compress(); - shutdown(); - } - } + private final Path sourceEPSG; /** * Provides a connection to the "SpatialMetadata" database, or {@code null} if the database already exists. - * The connection URL will reference the {@code SIS_DATA/Databases/spatial-metadata} directory in the Maven - * {@code target/classes} directory. + * The connection <abbr>URL</abbr> references the <code>SIS_DATA/Databases/{@value EmbeddedResources#EMBEDDED_DATABASE}</code> + * directory in the Maven {@code target/classes} directory. */ private final EmbeddedDataSource dataSource; @@ -98,12 +75,14 @@ public final class Generator extends ScriptProvider { * Creates a new database generator. */ Generator() throws URISyntaxException, IOException { - Path target = copyLicenseFiles(); + classesDirectory = directoryOf(EmbeddedResources.class); + Path target = classesDirectory; do target = target.getParent(); // Move to the root directory of classes. while (!target.getFileName().toString().startsWith("org.apache.sis.")); target = target.resolve("META-INF").resolve(DataDirectory.ENV); if (Files.isDirectory(target)) { dataSource = null; + sourceEPSG = null; return; } /* @@ -116,28 +95,51 @@ public final class Generator extends ScriptProvider { dataSource.setDataSourceName(Initializer.DATABASE); dataSource.setDatabaseName(target.resolve(EmbeddedResources.EMBEDDED_DATABASE).toString()); dataSource.setCreateDatabase("create"); + sourceEPSG = directoryOf(ScriptProvider.class); + } + + /** + * Returns the directory which contains the given class. + */ + private static Path directoryOf(final Class<?> member) throws URISyntaxException { + return Path.of(member.getResource(member.getSimpleName() + ".class").toURI()).getParent(); + } + + /** + * Generates the embedded resources in the {@code target/classes} directory if it does not already exists. + * See class Javadoc for more information. + * + * @throws Exception if a failure occurred while searching directories, + * executing <abbr>SQL</abbr> scripts, copying data or any other operation. + */ + final void createIfAbsent() throws Exception { + if (dataSource != null) { + copyLicenseFiles(); + createMetadata(); + createEPSG(); + compress(); + shutdown(); + } } /** - * Copies the EPSG terms of use from the {@code sis-epsg} module to this {@code sis-embedded-data} module. - * We copy those files ourselves instead than relying on {@code maven-resources-plugin} because a future - * version may combine more licenses in a single file. + * Copies the <abbr>EPSG</abbr> terms of use from the {@code sis-epsg} module to this {@code sis-embedded-data} module. + * If the <abbr>EPSG</abbr> data are not found, then this method does nothing. * - * @return the directory where the licenses have been copied. + * <p>We copy those files ourselves instead than relying on {@code maven-resources-plugin} + * because a future version may combine more licenses in a single file.</p> */ - private Path copyLicenseFiles() throws URISyntaxException, IOException { - final Class<?> consumer = EmbeddedResources.class; - final Path target = Path.of(consumer.getResource(consumer.getSimpleName() + ".class").toURI()).getParent(); + private void copyLicenseFiles() throws URISyntaxException, IOException { final String[] files = {"LICENSE.txt", "LICENSE.html"}; for (String file : files) { - try (InputStream in = openStream(file)) { - if (in == null) throw new FileNotFoundException(file); - Files.copy(in, target.resolve(file)); - } catch (FileAlreadyExistsException e) { - break; + final Path source = sourceEPSG.resolve(file); + if (Files.exists(source)) { + final Path target = classesDirectory.resolve(file); + if (Files.notExists(target)) { + Files.createLink(target, source); + } } } - return target; } /** @@ -146,7 +148,7 @@ public final class Generator extends ScriptProvider { * @throws FactoryException if an error occurred while creating or querying the database. */ private void createMetadata() throws MetadataStoreException, ReflectiveOperationException { - try (MetadataSource md = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null)) { + try (var md = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null)) { Method install = md.getClass().getDeclaredMethod("install"); install.setAccessible(true); install.invoke(md); @@ -154,12 +156,18 @@ public final class Generator extends ScriptProvider { } /** - * Creates the EPSG database schema. + * Creates the <abbr>EPSG</abbr> database schema. + * This method does nothing if the <abbr>EPSG</abbr> are not available. + * In the latter case, only the database will contain only free metadata. * * @throws FactoryException if an error occurred while creating or querying the database. + * @return whether the <abbr>EPSG</abbr> data were found. */ - private void createEPSG() throws FactoryException { - final Map<String,Object> properties = new HashMap<>(); + private boolean createEPSG() throws FactoryException { + if (Files.notExists(sourceEPSG.resolve("Data.sql"))) { + return false; + } + final var properties = new HashMap<String,Object>(); properties.put("dataSource", dataSource); properties.put("scriptProvider", this); /* @@ -173,16 +181,17 @@ public final class Generator extends ScriptProvider { if (!crs.getName().getCode().equals("WGS 84")) { throw new FactoryException("Unexpected CRS: " + crs.getName()); } + return true; } /** - * Compresses all tables in the EPSG schema. Compression can save space if there was many update + * Compresses all tables in all schema. Compression can save space if there was many update * or delete operations in the database. In the case of the database generated by this class, * the benefit is very small because the database is fresh. But it is still non-zero. */ private void compress() throws SQLException { try (Connection c = dataSource.getConnection()) { - List<String> tables = new ArrayList<>(80); // As (schema,table) pairs. + final var tables = new ArrayList<String>(80); // As (schema,table) pairs. try (ResultSet r = c.getMetaData().getTables(null, null, null, null)) { while (r.next()) { final String schema = r.getString("TABLE_SCHEM"); diff --git a/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/README.md b/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/README.md index bfd7a72114..115306fa6b 100644 --- a/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/README.md +++ b/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/README.md @@ -9,16 +9,13 @@ the following commands must be executed manually in a separated directory: ```shell svn checkout https://svn.apache.org/repos/asf/sis/data/non-free/ cd non-free/EPSG -export NON_FREE_DIR=$PWD +export EPSG_DIR=$PWD ``` -Then, the following commands must be executed with this directory as the current directory: +Then, the following commands (or something equivalent) should be executed +with the directory of this `README.md` file as the current directory: ```shell -ln --symbolic $NON_FREE_DIR/LICENSE.txt -ln --symbolic $NON_FREE_DIR/LICENSE.html -ln --symbolic $NON_FREE_DIR/Tables.sql -ln --symbolic $NON_FREE_DIR/Data.sql -ln --symbolic $NON_FREE_DIR/FKeys.sql -cd - +ln --symbolic $EPSG_DIR/LICENSE.* . +ln --symbolic $EPSG_DIR/*.sql . ``` diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/ScriptProviderTest.java b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/ScriptProviderTest.java index f18c0c8b77..71792815a2 100644 --- a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/ScriptProviderTest.java +++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/ScriptProviderTest.java @@ -80,11 +80,11 @@ public final strictfp class ScriptProviderTest { public void testResources() throws IOException { final InstallationResources provider = getInstance(); final String[] names = provider.getResourceNames("EPSG"); - assertArrayEquals(new String[] {"Prepare", "Tables.sql", "Data.sql", "FKeys.sql", "Finish"}, names); + assertArrayEquals(new String[] {"Prepare.sql", "Tables.sql", "Data.sql", "FKeys.sql", "Finish.sql"}, names); for (int i=0; i<names.length; i++) { try (BufferedReader in = provider.openScript("EPSG", i)) { // Just verify that we can read. - assertFalse(in.readLine().isEmpty()); + assertFalse(in.readLine().isBlank()); } } }
