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 <[email protected]>
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.