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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 70d35b06da Add support for `DataSource` created from JDBC URL in 
`StorageConnector`. Add documentation and minor cleanup.
70d35b06da is described below

commit 70d35b06da4121c6c5d3c6ce03bf2d82f4dcb0d4
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Aug 28 18:44:35 2024 +0200

    Add support for `DataSource` created from JDBC URL in `StorageConnector`.
    Add documentation and minor cleanup.
---
 .../org/apache/sis/metadata/sql/privy/Dialect.java |   3 +-
 .../main/module-info.java                          |  24 ++-
 .../main/org/apache/sis/storage/sql/SQLStore.java  |   2 +-
 .../apache/sis/storage/sql/SimpleFeatureStore.java |   4 +-
 .../sis/storage/sql/feature/CRSEncoding.java       |   1 +
 .../sis/storage/sql/feature/GeometryGetter.java    |   4 +-
 .../sis/storage/sql/feature/SpatialSchema.java     |   4 +-
 .../org/apache/sis/storage/sql/package-info.java   |  25 +--
 .../org/apache/sis/storage/sql/SQLStoreTest.java   |  21 ++-
 .../org/apache/sis/storage/StorageConnector.java   |  74 +++++---
 .../main/org/apache/sis/storage/URLDataSource.java | 198 +++++++++++++++++++++
 .../org/apache/sis/storage/internal/Resources.java |   5 +
 .../sis/storage/internal/Resources.properties      |   1 +
 .../sis/storage/internal/Resources_fr.properties   |   1 +
 .../main/org/apache/sis/util/privy/Constants.java  |   2 +-
 15 files changed, 320 insertions(+), 49 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
index a942ada0df..c1f2b16716 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
@@ -19,6 +19,7 @@ package org.apache.sis.metadata.sql.privy;
 import java.sql.SQLException;
 import java.sql.DatabaseMetaData;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
@@ -170,7 +171,7 @@ public enum Dialect {
         final String url = metadata.getURL();
         if (url != null) {
             int start = url.indexOf(':');
-            if (start >= 0 && "jdbc".equalsIgnoreCase((String) 
CharSequences.trimWhitespaces(url, 0, start))) {
+            if (start >= 0 && Constants.JDBC.equalsIgnoreCase((String) 
CharSequences.trimWhitespaces(url, 0, start))) {
                 final int end = url.indexOf(':', ++start);
                 if (end >= 0) {
                     final String protocol = (String) 
CharSequences.trimWhitespaces(url, start, end);
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
index 90a561b5a0..aa3eab4fe6 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
@@ -16,12 +16,32 @@
  */
 
 /**
- * SQL databases store.
+ * Data store for features in a <abbr>SQL</abbr> spatial database.
+ * This module expects a spatial schema conforms to the conventions described 
in the
+ * <a href="https://www.ogc.org/standards/sfs";>OGC Simple feature access - 
Part 2: SQL option</a>
+ * international standard, also known as <abbr>ISO</abbr> 19125-2.
+ *
+ * <h2>Difference with Geopackage</h2>
+ * Compared to the <abbr>OGC</abbr> Geopackage standard,
+ * this <abbr>SQL</abbr> module has the following differences:
+ *
+ * <ul>
+ *   <li>There is no discovery mechanism (e.g., no {@code "gpkg_contents"} 
table).
+ *       The tables to use as {@linkplain 
org.apache.sis.storage.sql.ResourceDefinition resource definitions}
+ *       must be specified explicitly.</li>
+ *   <li>Each feature table can contain an arbitrary number of geometry 
columns, including zero.
+ *       By contrast, Geopackage requires each feature table to have exactly 
one geometry column.</li>
+ *   <li>As a consequence of the above, this module makes no distinction 
between "features" table and "attributes" table.</li>
+ *   <li>This module supports <dfn>complex features</dfn>, i.e. features 
having associations to other features.
+ *       The associations are discovered automatically by following the 
foreigner keys.</li>
+ *   <li>Primary keys are optional. If present, they can be of any type (not 
necessarily integers) and can be composite
+ *       (made of many columns). By contrast, Geopackage mandates primary keys 
made of exactly one column of integers.</li>
+ * </ul>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.0
  */
 module org.apache.sis.storage.sql {
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
index 6cc17a02b7..ca6889ffdb 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
@@ -218,7 +218,7 @@ public abstract class SQLStore extends DataStore implements 
Aggregate {
      */
     protected SQLStore(final DataStoreProvider provider, final 
StorageConnector connector) throws DataStoreException {
         super(provider, connector);
-        source           = connector.getStorageAs(DataSource.class);
+        source           = connector.commit(DataSource.class, "SQL");
         geomLibrary      = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
         contentLocale    = connector.getOption(OptionKey.LOCALE);
         customizer       = connector.getOption(SchemaModifier.OPTION);
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java
index fc839f5b60..eb32c98b8e 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java
@@ -43,7 +43,9 @@ import org.apache.sis.util.ArgumentChecks;
  *       inferred by foreigner keys will be followed automatically.</li>
  * </ul>
  *
- * The mapping from table structures to feature types is described in the 
package Javadoc.
+ * Despite the {@code SimpleFeatureStore} class name, this class supports 
<dfn>complex features</dfn>,
+ * i.e. features having associations to other features.
+ * The associations are discovered automatically by following the foreigner 
keys.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/CRSEncoding.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/CRSEncoding.java
index 8b8e91df0b..69ed7be60a 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/CRSEncoding.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/CRSEncoding.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.sql.feature;
 
 /**
  * The encoding of Coordinate Reference Systems in a particular column, in 
preference order.
+ * The Geopackage specification said that WKT 2 has precedence over WKT 1.
  *
  * <p><b>Note:</b> the distinction between version 1 and 2 of <abbr>WKT</abbr> 
formats should not have been needed,
  * because a decent parser should be able to differentiate those two versions 
automatically based on the fact that
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
index 451ad045b9..fefebec715 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
@@ -126,8 +126,8 @@ final class GeometryGetter<G, V extends G> extends 
ValueGetter<V> {
             final int     flags        = wkb[3];
             final boolean bigEndian    = (flags & 0b000001) == 0;
             final int     envelopeType = (flags & 0b001110) >> 1;
-            final boolean isEmpty      = (flags & 0b010000) != 0;
-            final boolean extendedType = (flags & 0b100000) != 0;
+        //  final boolean isEmpty      = (flags & 0b010000) != 0;
+        //  final boolean extendedType = (flags & 0b100000) != 0;
             buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN : 
ByteOrder.LITTLE_ENDIAN);
             gpkgSrid = buffer.getInt(Integer.BYTES);
             // Skip header and envelope.
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
index cd2b2c3204..42b62ef88f 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
@@ -119,8 +119,8 @@ public enum SpatialSchema {
 
     /**
      * Name of the column for CRS definitions in Well-Known Text 
(<abbr>WKT</abbr>) format.
-     * Example: {@code "SRTEXT"}, {@code "DEFINITION"}.
-     * Entries are in no particular order.
+     * Example: {@code "SRTEXT"}, {@code "DEFINITION"}. Entries are in no 
particular order.
+     * The priority order is not defined by this map, but by the {@link 
CRSEncoding} enumeration.
      */
     final Map<CRSEncoding, String> crsDefinitionColumn;
 
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
index 1b38c39225..ec2b149779 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
@@ -25,18 +25,23 @@
  * to another feature (with transitive dependencies automatically resolved), 
and the other columns are represented
  * by {@link org.opengis.feature.AttributeType}.
  *
- * <p>The storage of spatial features in SQL databases is described by the
+ * <p>The storage of spatial features in <abbr>SQL</abbr> databases is 
described by the
  * <a href="https://www.ogc.org/standards/sfs";>OGC Simple feature access - 
Part 2: SQL option</a>
- * international standard, also known as ISO 19125-2. Implementation of 
geometric types and operations must
- * be provided by the database (sometimes through an extension, for example 
PostGIS on PostgreSQL databases).
- * This Java package uses those provided types and operations.</p>
+ * international standard, also known as ISO 19125-2.
+ * The implementation of geometric objects and their operations must be 
provided by the database.
+ * This is sometimes provided by an extension that needs to be installed 
explicitly.
+ * For example, when using PostgreSQL, the PostGIS extension is 
recommended.</p>
+ *
+ * <p>The tables to use as {@linkplain 
org.apache.sis.storage.sql.ResourceDefinition resource definitions}
+ * must be specified at construction time. There is no automatic discovery 
mechanism. Note that discovery
+ * may be done by other modules. For example, Geopackage module uses the 
{@code "gpkg_contents"} table.</p>
  *
  * <h2>Performance tips</h2>
  * <p>A subset of features can be obtained by applying filters on the stream 
returned by
  * {@link org.apache.sis.storage.FeatureSet#features(boolean)}.
  * While the filter can be any {@link java.util.function.Predicate},
  * performances will be much better if they are instances of {@link 
org.opengis.filter.Filter}
- * because Apache SIS will know how to translate some of them to SQL 
statements.</p>
+ * because Apache SIS will know how to translate some of them to 
<abbr>SQL</abbr> statements.</p>
  *
  * <p>In filter expressions like {@code ST_Intersects(A,B)} where the 
<var>A</var> and <var>B</var> parameters are
  * two sub-expressions evaluating to geometry values, if one of those 
expressions is a literal, then that literal
@@ -44,13 +49,9 @@
  * Coordinate Reference System of <var>A</var>. If <var>B</var> is a literal, 
Apache SIS can do this transformation
  * only once before to start the filtering process instead of every time that 
the filter needs to be evaluated.</p>
  *
- * <h2>Limitations</h2>
- * <ul>
- *   <li>Current implementation does not scan the {@code "GEOMETRY_COLUMNS"} 
(from Simple Feature Access)
- *       or {@code "gpkg_content"} (from GeoPackage) tables for a default list 
of feature tables.</li>
- *   <li>If a parent feature contains association to other features, those 
other features are created
- *       at the same time as the parent feature (no lazy instantiation 
yet).</li>
- * </ul>
+ * <p><b>Limitation:</b> if a parent feature contains association to other 
features (defined by foreigner keys),
+ * those other features are created at the same time as the parent feature. 
There is no lazy instantiation yet.
+ * Performances should be okay if each parent feature references only a small 
amount of children.</p>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index 6b38bac882..ff57652574 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -122,6 +122,15 @@ public final class SQLStoreTest extends TestOnAllDatabases 
{
         };
     }
 
+    /**
+     * Returns the storage connector to use for connecting to the test 
database.
+     * A new instance shall be created for each test, because each instance can
+     * be used only once.
+     */
+    private static StorageConnector connector(final TestDatabase database) {
+        return new StorageConnector(database.source);
+    }
+
     /**
      * 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.
@@ -139,19 +148,19 @@ public final class SQLStoreTest extends 
TestOnAllDatabases {
         }
         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);
+        testTableQuery(connector(database), table);
         /*
          * Verify using SQL statements instead of tables.
          */
-        verifyFetchCityTableAsQuery(connector);
-        verifyNestedSQLQuery(connector);
-        verifyLimitOffsetAndColumnSelectionFromQuery(connector);
-        verifyDistinctQuery(connector);
+        verifyFetchCityTableAsQuery(connector(database));
+        verifyNestedSQLQuery(connector(database));
+        verifyLimitOffsetAndColumnSelectionFromQuery(connector(database));
+        verifyDistinctQuery(connector(database));
         /*
          * Test on the table again, but with cyclic associations enabled.
          */
+        final StorageConnector connector = connector(database);
         connector.setOption(SchemaModifier.OPTION, new SchemaModifier() {
             @Override public boolean isCyclicAssociationAllowed(TableReference 
dependency) {
                 return true;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
index c74d312130..d563aca6bb 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
@@ -47,6 +47,7 @@ import javax.imageio.ImageIO;
 import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.ImageOutputStream;
 import java.sql.Connection;
+import java.sql.DriverManager;
 import java.sql.SQLException;
 import javax.sql.DataSource;
 import org.apache.sis.util.Debug;
@@ -57,6 +58,7 @@ import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.privy.Strings;
+import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.DefaultTreeTable;
@@ -231,6 +233,7 @@ public class StorageConnector implements Serializable {
         add(InputStream.class,       StorageConnector::createInputStream);
         add(OutputStream.class,      StorageConnector::createOutputStream);
         add(Reader.class,            StorageConnector::createReader);
+        add(DataSource.class,        StorageConnector::createDataSource);
         add(Connection.class,        StorageConnector::createConnection);
         add(ChannelDataInput.class,  
StorageConnector::createChannelDataInput);   // Undocumented case (SIS internal)
         add(ChannelDataOutput.class, 
StorageConnector::createChannelDataOutput);  // Undocumented case (SIS internal)
@@ -756,7 +759,7 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an 
instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link 
CharSequence} types,
      *           returns the string representation of their path.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link Path}, {@link URI}, {@link URL}, {@link File}:
@@ -764,14 +767,14 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an 
instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link 
CharSequence} types and
      *           that type can be converted to the requested type, returned 
the conversion result.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link ByteBuffer}:
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object can be 
obtained as described in bullet 2 of the
      *           {@code DataInput} section below, then this method returns the 
associated byte buffer.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link DataInput}:
@@ -779,16 +782,16 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@code DataInput}
      *           (including the {@link ImageInputStream} and {@link 
ImageOutputStream} types),
      *           then it is returned unchanged.</li>
-     *       <li>Otherwise if the input is an instance of {@link ByteBuffer}, 
then an {@link ImageInputStream}
+     *       <li>Otherwise, if the input is an instance of {@link ByteBuffer}, 
then an {@link ImageInputStream}
      *           backed by a read-only view of that buffer is created when 
first needed and returned.
      *           The properties (position, mark, limit) of the original buffer 
are unmodified.</li>
-     *       <li>Otherwise if the input is an instance of {@link Path}, {@link 
File},
+     *       <li>Otherwise, if the input is an instance of {@link Path}, 
{@link File},
      *           {@link URI}, {@link URL}, {@link CharSequence}, {@link 
InputStream} or
      *           {@link ReadableByteChannel}, then an {@link ImageInputStream} 
backed by a
      *           {@link ByteBuffer} is created when first needed and 
returned.</li>
-     *       <li>Otherwise if {@link ImageIO#createImageInputStream(Object)} 
returns a non-null value,
+     *       <li>Otherwise, if {@link ImageIO#createImageInputStream(Object)} 
returns a non-null value,
      *           then this value is cached and returned.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link ImageInputStream}:
@@ -801,9 +804,9 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link InputStream},
      *           then it is returned unchanged.</li>
-     *       <li>Otherwise if the above {@code ImageInputStream} can be 
created,
+     *       <li>Otherwise, if the above {@code ImageInputStream} can be 
created,
      *           returns a wrapper around that stream.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link Reader}:
@@ -811,23 +814,23 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link Reader},
      *           then it is returned unchanged.</li>
      *
-     *       <li>Otherwise if the above {@code InputStream} can be created, 
returns an {@link InputStreamReader}
+     *       <li>Otherwise, if the above {@code InputStream} can be created, 
returns an {@link InputStreamReader}
      *           using the encoding specified by {@link OptionKey#ENCODING} if 
any, or using the system default
      *           encoding otherwise.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link DataOutput}:
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@code DataOutput}
      *           (including the {@link ImageOutputStream} type), then it is 
returned unchanged.</li>
-     *       <li>Otherwise if the output is an instance of {@link Path}, 
{@link File},
+     *       <li>Otherwise, if the output is an instance of {@link Path}, 
{@link File},
      *           {@link URI}, {@link URL}, {@link CharSequence}, {@link 
OutputStream} or
      *           {@link WritableByteChannel}, then an {@link ImageInputStream} 
backed by a
      *           {@link ByteBuffer} is created when first needed and 
returned.</li>
-     *       <li>Otherwise if {@link ImageIO#createImageOutputStream(Object)} 
returns a non-null value,
+     *       <li>Otherwise, if {@link ImageIO#createImageOutputStream(Object)} 
returns a non-null value,
      *           then this value is cached and returned.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link ImageOutputStream}:
@@ -842,20 +845,30 @@ public class StorageConnector implements Serializable {
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
+     *   <li>{@link DataSource}:
+     *     <ul>
+     *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link DataSource},
+     *           then it is returned unchanged.</li>
+     *       <li>Otherwise, if the storage is convertible to an {@link URI} 
and the {@linkplain URI#getScheme()
+     *           URI scheme} is "jdbc" (ignoring case), then a data source 
delegating to {@link DriverManager}
+     *           is created when first needed and returned.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
      *   <li>{@link Connection}:
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link Connection},
      *           then it is returned unchanged.</li>
-     *       <li>Otherwise if the storage is an instance of {@link 
DataSource}, then a connection is obtained
+     *       <li>Otherwise, if the storage is convertible to a {@link 
DataSource}, then a connection is obtained
      *           when first needed and returned.</li>
-     *       <li>Otherwise this method returns {@code null}.</li>
+     *       <li>Otherwise, this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>Any other types:
      *     <ul>
      *       <li>If the storage given at construction time is already an 
instance of the requested type,
      *           returns it <i>as-is</i>.</li>
-     *       <li>Otherwise this method throws {@link 
IllegalArgumentException}.</li>
+     *       <li>Otherwise, this method throws {@link 
IllegalArgumentException}.</li>
      *     </ul>
      *   </li>
      * </ul>
@@ -1405,17 +1418,36 @@ public class StorageConnector implements Serializable {
         return in;
     }
 
+    /**
+     * Creates a database source if possible.
+     *
+     * <p>This method is one of the {@link #OPENERS} methods and should be 
invoked at most once per
+     * {@code StorageConnector} instance.</p>
+     *
+     * @return input/output, or {@code null} if none.
+     */
+    private DataSource createDataSource() throws DataStoreException {
+        final URI uri = getStorageAs(URI.class);
+        if (uri != null && Constants.JDBC.equalsIgnoreCase(uri.getScheme())) {
+            final var source = new URLDataSource(uri);
+            addView(DataSource.class, source, null, (byte) 0);
+            return source;
+        }
+        return null;
+    }
+
     /**
      * Creates a database connection if possible.
      *
      * <p>This method is one of the {@link #OPENERS} methods and should be 
invoked at most once per
      * {@code StorageConnector} instance.</p>
      *
-     * @return input, or {@code null} if none.
+     * @return input/output, or {@code null} if none.
      */
-    private Connection createConnection() throws SQLException {
-        if (storage instanceof DataSource) {
-            final Connection c = ((DataSource) storage).getConnection();
+    private Connection createConnection() throws SQLException, 
DataStoreException {
+        final DataSource source = getStorageAs(DataSource.class);
+        if (source != null) {
+            final Connection c = source.getConnection();
             addView(Connection.class, c, null, (byte) 0);
             return c;
         }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/URLDataSource.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/URLDataSource.java
new file mode 100644
index 0000000000..a4857e14bd
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/URLDataSource.java
@@ -0,0 +1,198 @@
+/*
+ *    Geotoolkit.org - An Open Source Java GIS Toolkit
+ *    http://www.geotoolkit.org
+ *
+ *    (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
+ *    (C) 2009-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.apache.sis.storage;
+
+import java.net.URI;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogRecord;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.DatabaseMetaData;
+import javax.sql.DataSource;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.storage.internal.Resources;
+import static org.apache.sis.storage.base.StoreUtilities.LOGGER;
+
+
+/**
+ * A data source which gets the connections from a {@link DriverManager}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @author Guilhem Legal (Geomatys)
+ */
+final class URLDataSource implements DataSource {
+    /**
+     * The driver names of the connection returned by {@code URLDataSource}.
+     * This is used for logging purposes only.
+     */
+    private static final Set<String> DRIVERS = new HashSet<>();
+
+    /**
+     * The URL to use for connecting to the database.
+     * This field can not be {@code null}.
+     */
+    private final String url;
+
+    /**
+     * Creates a data source for the given URL.
+     * The value of {@link URI#getScheme()} should be {@code "jdbc"}, ignoring 
case.
+     *
+     * @param url The URL to use for connecting to the database.
+     */
+    public URLDataSource(final URI url) {
+        this.url = url.toString();
+    }
+
+    /**
+     * Logs the driver version if this is the first time we get a connection 
for that driver.
+     */
+    private static Connection log(final Connection connection) throws 
SQLException {
+        if (LOGGER.isLoggable(Level.CONFIG)) {
+            final DatabaseMetaData metadata = connection.getMetaData();
+            final String name = metadata.getDriverName();
+            final boolean log;
+            synchronized (DRIVERS) {
+                log = DRIVERS.add(name);
+            }
+            if (log) {
+                final LogRecord record = Resources.forLocale(null)
+                        .getLogRecord(Level.CONFIG, 
Resources.Keys.UseJdbcDriverVersion_3, name,
+                                      metadata.getDriverMajorVersion(), 
metadata.getDriverMinorVersion());
+                Logging.completeAndLog(LOGGER, StorageConnector.class, 
"getStorageAs", record);
+            }
+        }
+        return connection;
+    }
+
+    /**
+     * Delegates to {@link DriverManager}.
+     *
+     * @throws SQLException If the connection can not be established.
+     */
+    @Override
+    public Connection getConnection() throws SQLException {
+        return log(DriverManager.getConnection(url));
+    }
+
+    /**
+     * Delegates to {@link DriverManager}.
+     *
+     * @throws SQLException If the connection can not be established.
+     */
+    @Override
+    public Connection getConnection(String username, String password) throws 
SQLException {
+        return log(DriverManager.getConnection(url, username, password));
+    }
+
+    /**
+     * Delegates to {@link DriverManager}.
+     */
+    @Override
+    public PrintWriter getLogWriter() {
+        return DriverManager.getLogWriter();
+    }
+
+    /**
+     * Delegates to {@link DriverManager}. It is better to avoid
+     * calling this method since it has a system-wide effect.
+     */
+    @Override
+    public void setLogWriter(final PrintWriter out) {
+        DriverManager.setLogWriter(out);
+    }
+
+    /**
+     * Delegates to {@link DriverManager}.
+     */
+    @Override
+    public int getLoginTimeout() {
+        return DriverManager.getLoginTimeout();
+    }
+
+    /**
+     * Delegates to {@link DriverManager}. It is better to avoid
+     * calling this method since it has a system-wide effect.
+     */
+    @Override
+    public void setLoginTimeout(final int seconds) {
+        DriverManager.setLoginTimeout(seconds);
+    }
+
+    /**
+     * Returns (@code false} in all cases, since this class is not a wrapper 
(omitting {@code DriverManager}).
+     */
+    @Override
+    public boolean isWrapperFor(Class<?> iface) {
+        return false;
+    }
+
+    /**
+     * Throws an exception in all cases, since this class is not a wrapper 
(omitting {@code DriverManager}).
+     *
+     * @param <T> Ignored.
+     */
+    @Override
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        throw new SQLException();
+    }
+
+    /**
+     * Compares this data source with the given object for equality.
+     *
+     * @param other The object to compare with this data source.
+     * @return {@code true} if both objects are equal.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        return (other instanceof URLDataSource) && url.equals(((URLDataSource) 
other).url);
+    }
+
+    /**
+     * Returns a hash code value for this data source.
+     *
+     * @return A hash code value for this data source.
+     */
+    @Override
+    public int hashCode() {
+        return url.hashCode() ^ 335483867;
+    }
+
+    /**
+     * Returns a string representation of this data source.
+     */
+    @Override
+    public String toString() {
+        return Classes.getShortClassName(this) + "[\"" + url + "\"]";
+    }
+
+    /**
+     * Returns the parent logger for this data source.
+     *
+     * @return the parent Logger for this data source
+     */
+    @Override
+    public Logger getParentLogger() {
+        return LOGGER;
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
index 39f32775a0..efa18420f6 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
@@ -458,6 +458,11 @@ public class Resources extends IndexedResourceBundle {
          */
         public static final short UnknownFormatFor_1 = 14;
 
+        /**
+         * Using {0} JDBC driver version {1}.{2}.
+         */
+        public static final short UseJdbcDriverVersion_3 = 82;
+
         /**
          * Used only if this information is not encoded with the data.
          */
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
index 4e433e2b72..b0eb6f1e38 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
@@ -100,3 +100,4 @@ UnexpectedNumberOfCoordinates_4   = The \u201c{0}\u201d 
feature at {1} has a {3}
 UnfilteredData                    = Unfiltered data.
 UnknownFormatFor_1                = Format of \u201c{0}\u201d is not 
recognized.
 UsedOnlyIfNotEncoded              = Used only if this information is not 
encoded with the data.
+UseJdbcDriverVersion_3            = Using {0} JDBC driver version {1}.{2}.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
index 6b742719e3..6b64a03e76 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
@@ -105,3 +105,4 @@ UnexpectedNumberOfCoordinates_4   = L\u2019entit\u00e9 
nomm\u00e9e \u00ab\u202f{
 UnfilteredData                    = Donn\u00e9es non-filtr\u00e9es.
 UnknownFormatFor_1                = Le format de \u00ab\u202f{0}\u202f\u00bb 
n\u2019est pas reconnu.
 UsedOnlyIfNotEncoded              = Utilis\u00e9 seulement si cette 
information n\u2019est pas encod\u00e9e avec les donn\u00e9es.
+UseJdbcDriverVersion_3            = Utilise le pilote JDBC 
\u00ab\u202f{0}\u202f\u00bb version {1}.{2}.
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
index 10179de258..de6ecf2cf6 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
@@ -100,7 +100,7 @@ public final class Constants extends Static {
     /**
      * The {@value} protocol.
      */
-    public static final String HTTP = "http", HTTPS = "https";
+    public static final String HTTP = "http", HTTPS = "https", JDBC = "jdbc";
 
     /**
      * The {@value} code space.


Reply via email to