All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rgi.geopackage.metadata.GeoPackageMetadata Maven / Gradle / Ivy

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