com.rgi.geopackage.metadata.GeoPackageMetadata Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swagd Show documentation
Show all versions of swagd Show documentation
SWAGD: Software to Aggregate Geospatial Data
The newest version!
/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.geopackage.metadata;
import java.net.URI;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import com.rgi.common.util.jdbc.ResultSetStream;
import com.rgi.geopackage.utility.DatabaseUtility;
import com.rgi.geopackage.utility.SelectBuilder;
import com.rgi.geopackage.verification.VerificationIssue;
import com.rgi.geopackage.verification.VerificationLevel;
/**
* @author Luke Lambert
*
*/
public class GeoPackageMetadata
{
/**
* Constructor
*
* @param databaseConnection
* The open connection to the database that contains a GeoPackage
*/
public GeoPackageMetadata(final Connection databaseConnection)
{
this.databaseConnection = databaseConnection;
}
/**
* Metadata requirements this GeoPackage failed to meet
* @param verificationLevel
* Controls the level of verification testing performed
* @return The metadata GeoPackage requirements this GeoPackage fails to conform to
* @throws SQLException throws when the {@link MetadataVerifier#MetadataVerifier} throws an SQLException
*/
public Collection getVerificationIssues(final VerificationLevel verificationLevel) throws SQLException
{
return new MetadataVerifier(this.databaseConnection, verificationLevel).getVerificationIssues();
}
/**
* Creates an entry in the GeoPackage metadata table
*
* @param scope
* Metadata scope
* @param standardUri
* URI reference to the metadata structure definition authority
* @param mimeType
* MIME encoding of metadata
* @param metadata
* Metadata text
* @return Returns the newly added {@link Metadata} object
* @throws SQLException
* if a database access error occurs, this method is called
* while participating in a distributed transaction, if this
* method is called on a closed connection or this Connection
* object is in auto-commit mode, or if the method getMetadata()
* throws or other various SQLExceptions occur
*/
public Metadata addMetadata(final Scope scope,
final URI standardUri,
final MimeType mimeType,
final String metadata) throws SQLException
{
if(scope == null)
{
throw new IllegalArgumentException("Scope may not be null");
}
if(standardUri == null)
{
throw new IllegalArgumentException("Standard URI may not be null");
}
if(mimeType == null)
{
throw new IllegalArgumentException("Mime type may not be null");
}
if(metadata == null)
{
throw new IllegalArgumentException("Metadata may not be null");
}
final Metadata existingMetadata = this.getMetadata(scope.toString(),
standardUri.toString(),
mimeType.toString(),
metadata);
if(existingMetadata != null)
{
return existingMetadata;
}
try
{
this.createMetadataTableNoCommit(); // Create the metadata table if it doesn't exist
final String insertMetadataSql = String.format("INSERT INTO %s (%s, %s, %s, %s) VALUES (?, ?, ?, ?)",
GeoPackageMetadata.MetadataTableName,
"md_scope",
"md_standard_uri",
"mime_type",
"metadata");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(insertMetadataSql))
{
preparedStatement.setString(1, scope.toString());
preparedStatement.setString(2, standardUri.toString());
preparedStatement.setString(3, mimeType.toString());
preparedStatement.setString(4, metadata);
preparedStatement.executeUpdate();
}
this.databaseConnection.commit();
return this.getMetadata(scope.toString(),
standardUri.toString(),
mimeType.toString(),
metadata);
}
catch(final Exception ex)
{
this.databaseConnection.rollback();
throw ex;
}
}
/**
* Creates an entry in the GeoPackage metadata reference table
*
* @param referenceScope
* Reference scope
* @param tableName
* Name of the table to which this metadata reference applies, or
* NULL for referenceScope of 'geopackage'
* @param columnName
* Name of the column to which this metadata reference applies;
* NULL for referenceScope of 'geopackage','table' or 'row', or
* the name of a column in the tableName table for referenceScope
* of 'column' or 'row/col'
* @param rowIdentifier
* NULL for referenceScope of 'geopackage', 'table' or 'column',
* or the rowed of a row record in the table_name table for
* referenceScope of 'row' or 'row/col'
* @param fileIdentifier
* gpkg_metadata table identifier column value for the metadata
* to which this gpkg_metadata_reference applies
* @param parentIdentifier
* gpkg_metadata table identifier column value for the
* hierarchical parent gpkg_metadata for the gpkg_metadata to
* which this gpkg_metadata_reference applies, or NULL if file
* identifier forms the root of a metadata hierarchy
* @return Returns the newly added {@link MetadataReference} object
* @throws SQLException
* throws if various SQLExceptions occur
*/
public MetadataReference addMetadataReference(final ReferenceScope referenceScope,
final String tableName,
final String columnName,
final Integer rowIdentifier,
final Metadata fileIdentifier,
final Metadata parentIdentifier) throws SQLException
{
if(referenceScope == null)
{
throw new IllegalArgumentException("Reference scope may not be null");
}
if(referenceScope == ReferenceScope.GeoPackage && tableName != null)
{
throw new IllegalArgumentException("Reference scopes of 'geopackage' must have null for the associated table name, and other reference scope values must have non-null table names"); // Requirement 72
}
if(!ReferenceScope.isColumnScope(referenceScope) && columnName != null)
{
throw new IllegalArgumentException("Reference scopes 'geopackage', 'table' or 'row' must have a null column name. Reference scope values of 'column' or 'row/col' must have a non-null column name"); // Requirement 73
}
if(ReferenceScope.isRowScope(referenceScope) && rowIdentifier == null)
{
throw new IllegalArgumentException(String.format("Reference scopes of 'geopackage', 'table' or 'column' must have a null row identifier. Reference scopes of 'row' or 'row/col', must contain a reference to a row record in the '%s' table",
tableName)); // Requirement 74
}
if(tableName != null && tableName.isEmpty())
{
throw new IllegalArgumentException("If table name is non-null, it may not be empty");
}
if(columnName != null && columnName.isEmpty())
{
throw new IllegalArgumentException("If column name is non-null, it may not be empty");
}
if(fileIdentifier == null)
{
throw new IllegalArgumentException("File identifier may not be null");
}
// TODO test referential integrity for table, column and row parameters - instead of using a table name, an Content object can be used
final Integer parentIdInteger = parentIdentifier == null ? null
: parentIdentifier.getIdentifier();
final MetadataReference existingMetadataReference = this.getMetadataReference(referenceScope.getText(),
tableName,
columnName,
rowIdentifier,
fileIdentifier.getIdentifier(),
parentIdInteger);
if(existingMetadataReference != null)
{
return existingMetadataReference;
}
try
{
this.createMetadataReferenceTableNoCommit(); // Create the metadata reference table if it doesn't exist
final String insertMetadataSql = String.format("INSERT INTO %s (%s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?)",
GeoPackageMetadata.MetadataReferenceTableName,
"reference_scope",
"table_name",
"column_name",
"row_id_value",
"md_file_id",
"md_parent_id");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(insertMetadataSql))
{
preparedStatement.setString(1, referenceScope.getText());
preparedStatement.setString(2, tableName);
preparedStatement.setString(3, columnName);
preparedStatement.setObject(4, rowIdentifier);
preparedStatement.setInt (5, fileIdentifier.getIdentifier());
preparedStatement.setObject(6, parentIdInteger);
preparedStatement.executeUpdate();
}
this.databaseConnection.commit();
final MetadataReference metadataReference = this.getMetadataReference(referenceScope.getText(),
tableName,
columnName,
rowIdentifier,
fileIdentifier.getIdentifier(),
parentIdInteger);
return metadataReference;
}
catch(final Exception ex)
{
this.databaseConnection.rollback();
throw ex;
}
}
/**
* Gets all entries in the GeoPackage metadata table
*
* @return Returns a collection of {@link Metadata} objects
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public Collection getMetadata() throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataTableName))
{
return Collections.emptyList();
}
final String metadataQuerySql = String.format("SELECT %s, %s, %s, %s, %s FROM %s;",
"id",
"md_scope",
"md_standard_uri",
"mime_type",
"metadata",
GeoPackageMetadata.MetadataTableName);
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(metadataQuerySql);
ResultSet resultSets = preparedStatement.executeQuery())
{
return ResultSetStream.getStream(resultSets)
.map(resultSet -> { try
{
return new Metadata(resultSet.getInt (1),
resultSet.getString(2),
resultSet.getString(3),
resultSet.getString(4),
resultSet.getString(5));
}
catch(final Exception ex)
{
ex.printStackTrace();
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
/**
* Gets an entry in the reference table which matches the supplied primary
* key
*
* @param identifier
* Metadata primary key
* @return Returns an instance of {@link Metadata} representing an entry in
* the GeoPackage metadata table, or null if no entry matches the
* supplied criteria
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public Metadata getMetadata(final int identifier) throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataTableName))
{
return null;
}
final String metadataQuerySql = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s = ? LIMIT 1;",
"md_scope",
"md_standard_uri",
"mime_type",
"metadata",
GeoPackageMetadata.MetadataTableName,
"id");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(metadataQuerySql))
{
preparedStatement.setInt(1, identifier);
try(ResultSet result = preparedStatement.executeQuery())
{
if(result.isBeforeFirst())
{
return new Metadata(identifier,
result.getString(1), // scope
result.getString(2), // URI
result.getString(3), // mime type
result.getString(4)); // metadata
}
}
return null;
}
}
/**
* Gets all entries in the GeoPackage metadata reference table
*
* @return Returns a collection of {@link MetadataReference} objects
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public Collection getMetadataReferences() throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataReferenceTableName))
{
return Collections.emptyList();
}
final String metadataReferenceQuerySql = String.format("SELECT %s, %s, %s, %s, %s, %s, %s FROM %s;",
"reference_scope",
"table_name",
"column_name",
"row_id_value",
"timestamp",
"md_file_id",
"md_parent_id",
GeoPackageMetadata.MetadataReferenceTableName);
try(final PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(metadataReferenceQuerySql);
final ResultSet results = preparedStatement.executeQuery())
{
return ResultSetStream.getStream(results)
.map(resultSet -> { try
{
return new MetadataReference( resultSet.getString(1), // reference Scope
resultSet.getString(2), // table name
resultSet.getString(3), // column name
(Integer)resultSet.getObject(4), // row identifier
resultSet.getString(5), // timestamp
resultSet.getInt (6), // file identifier
(Integer)resultSet.getObject(7)); // parent identifier
}
catch(final SQLException ex)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
/**
* Creates the GeoPackage metadata table
*
*
* **WARNING** this does not do a database commit. It is expected
* that this transaction will always be paired with others that need to be
* committed or rollback as a single transaction.
*
* @throws SQLException
*/
protected void createMetadataTableNoCommit() throws SQLException
{
// Create the tile matrix set table or view
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataTableName))
{
try(Statement statement = this.databaseConnection.createStatement())
{
statement.executeUpdate(this.getMetadataTableCreationSql());
}
}
}
/**
* Creates the GeoPackage metadata reference table
*
*
* **WARNING** this does not do a database commit. It is expected
* that this transaction will always be paired with others that need to be
* committed or rollback as a single transaction.
*
* @throws SQLException
*/
protected void createMetadataReferenceTableNoCommit() throws SQLException
{
// Create the tile matrix table or view
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataReferenceTableName))
{
try(Statement statement = this.databaseConnection.createStatement())
{
statement.executeUpdate(this.getMetadataReferenceTableCreationSql());
}
}
}
@SuppressWarnings("static-method")
protected String getMetadataTableCreationSql()
{
// http://www.geopackage.org/spec/#gpkg_metadata_cols
// http://www.geopackage.org/spec/#gpkg_metadata_sql
return "CREATE TABLE " + GeoPackageMetadata.MetadataTableName + "\n" +
"(id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL UNIQUE, -- Metadata primary key\n" +
" md_scope TEXT NOT NULL DEFAULT 'dataset', -- Case sensitive name of the data scope to which this metadata applies; see Metadata Scopes\n" +
" md_standard_uri TEXT NOT NULL, -- URI reference to the metadata structure definition authority\n" +
" mime_type TEXT NOT NULL DEFAULT 'text/xml', -- MIME encoding of metadata\n" +
" metadata TEXT NOT NULL -- metadata\n" +
");";
}
@SuppressWarnings("static-method")
protected String getMetadataReferenceTableCreationSql()
{
// http://www.geopackage.org/spec/#gpkg_metadata_reference_cols
// http://www.geopackage.org/spec/#gpkg_metadata_reference_sql
return "CREATE TABLE " + GeoPackageMetadata.MetadataReferenceTableName + "\n" +
"(reference_scope TEXT NOT NULL, -- Lowercase metadata reference scope; one of 'geopackage', 'table','column', 'row', 'row/col'\n" +
" table_name TEXT, -- Name of the table to which this metadata reference applies, or NULL for reference_scope of 'geopackage'\n" +
" column_name TEXT, -- Name of the column to which this metadata reference applies; NULL for reference_scope of 'geopackage','table' or 'row', or the name of a column in the table_name table for reference_scope of 'column' or 'row/col'\n" +
" row_id_value INTEGER, -- NULL for reference_scope of 'geopackage', 'table' or 'column', or the rowed of a row record in the table_name table for reference_scope of 'row' or 'row/col'\n" +
" timestamp DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), -- timestamp value in ISO 8601 format as defined by the strftime function '%Y-%m-%dT%H:%M:%fZ' format string applied to the current time\n" +
" md_file_id INTEGER NOT NULL, -- gpkg_metadata table id column value for the metadata to which this gpkg_metadata_reference applies\n" +
" md_parent_id INTEGER, -- gpkg_metadata table id column value for the hierarchical parent gpkg_metadata for the gpkg_metadata to which this gpkg_metadata_reference applies, or NULL if md_file_id forms the root of a metadata hierarchy\n" +
" CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES gpkg_metadata(id),\n" +
" CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES gpkg_metadata(id));";
}
/**
* Gets an entry in the metadata table that matches the supplied criteria
*
* @param scope
* Case sensitive name of the data scope to which this metadata applies
* @param standardUri
* URI reference to the metadata structure definition authority
* @param mimeType
* MIME encoding of metadata
* @param metadata
* Metadata text
* @return Returns an an instance of {@link Metadata} representing an entry in the GeoPackage metadata table, or null if no entry matches the supplied criteria
* @throws SQLException
*/
private Metadata getMetadata(final String scope,
final String standardUri,
final String mimeType,
final String metadata) throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataTableName))
{
return null;
}
final String metadataQuerySql = String.format("SELECT %s FROM %s WHERE %s = ? AND %s = ? AND %s = ? AND %s = ? LIMIT 1;",
"id",
GeoPackageMetadata.MetadataTableName,
"md_scope",
"md_standard_uri",
"mime_type",
"metadata");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(metadataQuerySql))
{
preparedStatement.setString(1, scope);
preparedStatement.setString(2, standardUri);
preparedStatement.setString(3, mimeType);
preparedStatement.setString(4, metadata);
try(ResultSet result = preparedStatement.executeQuery())
{
if(result.isBeforeFirst())
{
return new Metadata(result.getInt(1), // identifier
scope,
standardUri,
mimeType,
metadata);
}
return null;
}
}
}
/**
* Gets an entry in the metadata reference table that matches the supplied criteria
*
* @param referenceScope
* Reference scope
* @param tableName
* Name of the table to which this metadata reference applies, or NULL for referenceScope of 'geopackage'
* @param columnName
* Name of the column to which this metadata reference applies; NULL for referenceScope of 'geopackage','table' or 'row', or the name of a column in the tableName table for referenceScope of 'column' or 'row/col'
* @param rowIdentifier
* NULL for referenceScope of 'geopackage', 'table' or 'column', or the rowed of a row record in the table_name table for referenceScope of 'row' or 'row/col'
* @param fileIdentifier
* gpkg_metadata table identifier column value for the metadata to which this gpkg_metadata_reference applies
* @param parentIdentifier
* gpkg_metadata table identifier column value for the hierarchical parent gpkg_metadata for the gpkg_metadata to which this gpkg_metadata_reference applies, or NULL if file identifier forms the root of a metadata hierarchy
* @return Returns an instance of {@link MetadataReference} representing an entry in the GeoPackage metadata reference table, or null if no entry matches the supplied criteria
* @throws SQLException
*/
private MetadataReference getMetadataReference(final String referenceScope,
final String tableName,
final String columnName,
final Integer rowIdentifier,
final int fileIdentifier,
final Integer parentIdentifier) throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageMetadata.MetadataReferenceTableName))
{
return null;
}
try(final SelectBuilder selectStatement = new SelectBuilder(this.databaseConnection,
GeoPackageMetadata.MetadataReferenceTableName,
Arrays.asList("timestamp"),
Arrays.asList(new AbstractMap.SimpleImmutableEntry<>("reference_scope", referenceScope),
new AbstractMap.SimpleImmutableEntry<>("table_name", tableName),
new AbstractMap.SimpleImmutableEntry<>("column_name", columnName),
new AbstractMap.SimpleImmutableEntry<>("row_id_value", rowIdentifier),
new AbstractMap.SimpleImmutableEntry<>("md_file_id", fileIdentifier),
new AbstractMap.SimpleImmutableEntry<>("md_parent_id", parentIdentifier)));
final ResultSet result = selectStatement.executeQuery())
{
if(result.isBeforeFirst())
{
return new MetadataReference(referenceScope,
tableName,
columnName,
rowIdentifier,
result.getString(1), // timestamp
fileIdentifier,
parentIdentifier);
}
return null;
}
}
private final Connection databaseConnection;
/**
* The Date value in ISO 8601 format as defined by the strftime function %Y-%m-%dT%H:%M:%fZ format string applied to the current time
*/
public final SimpleDateFormat DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
/**
* The String name "gpkg_metadata" of the database Metadata table containing
* the metadata of the GeoPackage
* (http://www.geopackage.org/spec/#_metadata_table)
*/
public final static String MetadataTableName = "gpkg_metadata";
/**
* The String name "gpkg_metadata_reference" of the database Metadata
* Reference table containing the metadata references of the GeoPackage
* http://www.geopackage.org/spec/#_metadata_reference_table
*/
public final static String MetadataReferenceTableName = "gpkg_metadata_reference";
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy