sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1770513 - in /sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql: MetadataProxy.java MetadataSource.java MetadataStoreException.java ResultPool.java
Date Sat, 19 Nov 2016 20:26:40 GMT
Author: desruisseaux
Date: Sat Nov 19 20:26:40 2016
New Revision: 1770513

URL: http://svn.apache.org/viewvc?rev=1770513&view=rev
Log:
Ported MetadataSource.search(Object) implementation.

Added:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
  (with props)
Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ResultPool.java

Added: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java?rev=1770513&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
(added)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
[UTF-8] Sat Nov 19 20:26:40 2016
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata.sql;
+
+
+/**
+ * Interface for metadata that are implemented by a proxy class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+interface MetadataProxy {
+    /**
+     * Returns the identifier (primary key) of this metadata if it is using the given source,
+     * or {@code null} otherwise.
+     */
+    String identifier(MetadataSource source);
+}

Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java?rev=1770513&r1=1770512&r2=1770513&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
[UTF-8] Sat Nov 19 20:26:40 2016
@@ -18,13 +18,21 @@ package org.apache.sis.metadata.sql;
 
 import java.util.Set;
 import java.util.Map;
+import java.util.HashSet;
 import java.util.HashMap;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.lang.reflect.Method;
+import java.sql.ResultSet;
+import java.sql.Statement;
 import java.sql.SQLException;
+import java.sql.SQLNonTransientException;
 import javax.sql.DataSource;
 import org.opengis.annotation.UML;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.KeyNamePolicy;
@@ -34,6 +42,8 @@ import org.apache.sis.metadata.iso.distr
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.SystemListener;
 import org.apache.sis.internal.metadata.sql.Initializer;
+import org.apache.sis.internal.metadata.sql.SQLBuilder;
+import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -64,10 +74,10 @@ import org.apache.sis.util.iso.Types;
  * @version 0.8
  * @module
  */
-public class MetadataSource {
+public class MetadataSource implements AutoCloseable {
     /**
      * The column name used for the identifiers. We do not quote this identifier;
-     * we will let the database uses its own convention.
+     * we will let the database uses its own lower-case / upper-case convention.
      */
     static final String ID_COLUMN = "ID";
 
@@ -141,8 +151,13 @@ public class MetadataSource {
      */
     public static MetadataSource getDefault() throws MetadataStoreException {
         MetadataSource ms = instance;
-        if (ms == null) try {
-            final DataSource dataSource = Initializer.getDataSource();
+        if (ms == null) {
+            final DataSource dataSource;
+            try {
+                dataSource = Initializer.getDataSource();
+            } catch (Exception e) {
+                throw new MetadataStoreException(Errors.format(Errors.Keys.CanNotConnectTo_1,
Initializer.JNDI), e);
+            }
             if (dataSource == null) {
                 throw new MetadataStoreException(Initializer.unspecified(null));
             }
@@ -152,10 +167,6 @@ public class MetadataSource {
                     instance = ms = new MetadataSource(MetadataStandard.ISO_19115, dataSource,
"metadata");
                 }
             }
-        } catch (MetadataStoreException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new MetadataStoreException(Errors.format(Errors.Keys.CanNotConnectTo_1,
Initializer.JNDI), e);
         }
         return ms;
     }
@@ -166,11 +177,8 @@ public class MetadataSource {
      * @param  standard    the metadata standard to implement.
      * @param  dataSource  the source for getting a connection to the database.
      * @param  schema      the schema were metadata are expected to be found, or {@code null}
if none.
-     * @throws SQLException if the connection to the given database can not be established.
      */
-    public MetadataSource(final MetadataStandard standard, final DataSource dataSource, final
String schema)
-            throws SQLException
-    {
+    public MetadataSource(final MetadataStandard standard, final DataSource dataSource, final
String schema) {
         ArgumentChecks.ensureNonNull("standard",   standard);
         ArgumentChecks.ensureNonNull("dataSource", dataSource);
         this.standard = standard;
@@ -257,6 +265,187 @@ public class MetadataSource {
     }
 
     /**
+     * If the given metadata is a proxy generated by this {@code MetadataSource}, returns
the
+     * identifier of that proxy. Such metadata do not need to be inserted again in the database.
+     *
+     * @param  metadata  the metadata to test.
+     * @return the identifier (primary key), or {@code null} if the given metadata is not
a proxy.
+     */
+    final String proxy(final Object metadata) {
+        return (metadata instanceof MetadataProxy) ? ((MetadataProxy) metadata).identifier(this)
: null;
+    }
+
+    /**
+     * Searches for the given metadata in the database. If such metadata is found, then its
+     * identifier (primary key) is returned. Otherwise this method returns {@code null}.
+     *
+     * @param  metadata  the metadata to search for.
+     * @return the identifier of the given metadata, or {@code null} if none.
+     * @throws MetadataStoreException if the metadata object does not implement a metadata
interface
+     *         of the expected package, or if an error occurred while searching in the database.
+     */
+    public String search(final Object metadata) throws MetadataStoreException {
+        ArgumentChecks.ensureNonNull("metadata", metadata);
+        String identifier = proxy(metadata);
+        if (identifier == null) {
+            /*
+             * Code lists do not need to be stored in the database. Some code list tables
may
+             * be present in the database in order to ensure foreigner key constraints, but
+             * those tables are not used in any way by the org.apache.sis.metadata.sql package.
+             */
+            if (metadata instanceof CodeList<?>) {
+                identifier = ((CodeList<?>) metadata).name();
+            } else {
+                final String table;
+                final Map<String,Object> asMap;
+                try {
+                    table = getTableName(standard.getInterface(metadata.getClass()));
+                    asMap = asMap(metadata);
+                } catch (ClassCastException e) {
+                    throw new MetadataStoreException(Errors.format(
+                            Errors.Keys.IllegalArgumentClass_2, "metadata", metadata.getClass()));
+                }
+                synchronized (statements) {
+                    try (Statement stmt = statements.connection().createStatement()) {
+                        identifier = search(table, null, asMap, stmt, statements.helper());
+                    } catch (SQLException e) {
+                        throw new MetadataStoreException(e);
+                    }
+                }
+            }
+        }
+        return identifier;
+    }
+
+    /**
+     * Searches for the given metadata in the database. If such metadata is found, then its
+     * identifier (primary key) is returned. Otherwise this method returns {@code null}.
+     *
+     * @param  table     the table where to search.
+     * @param  columns   the table columns as given by {@link #getExistingColumns(String)},
or {@code null}.
+     * @param  metadata  a map view of the metadata to search for.
+     * @param  stmt      the statement to use for executing the query.
+     * @param  helper    an helper class for creating the SQL query.
+     * @return the identifier of the given metadata, or {@code null} if none.
+     * @throws SQLException if an error occurred while searching in the database.
+     */
+    final String search(final String table, Set<String> columns, final Map<String,Object>
metadata,
+            final Statement stmt, final SQLBuilder helper) throws SQLException
+    {
+        assert Thread.holdsLock(statements);
+        helper.clear();
+        for (final Map.Entry<String,Object> entry : metadata.entrySet()) {
+            /*
+             * Gets the value and the column where this value is stored. If the value is
non-null,
+             * then the column must exist otherwise the metadata will be considered as not
found.
+             */
+            Object value = extractFromCollection(entry.getValue());
+            final String column = entry.getKey();
+            if (columns == null) {
+                columns = getExistingColumns(table);
+            }
+            if (!columns.contains(column)) {
+                if (value != null) {
+                    return null;            // The column was mandatory for the searched
metadata.
+                } else {
+                    continue;               // Do not include a non-existent column in the
SQL query.
+                }
+            }
+            /*
+             * Tests if the value is another metadata, in which case we will invoke this
method recursively.
+             * Note that if a metadata dependency is not found, we can stop the whole process
immediately.
+             */
+            if (value != null) {
+                if (value instanceof CodeList<?>) {
+                    value = ((CodeList<?>) value).name();
+                } else {
+                    String dependency = proxy(value);
+                    if (dependency != null) {
+                        value = dependency;
+                    } else {
+                        final Class<?> type = value.getClass();
+                        if (standard.isMetadata(type)) {
+                            dependency = search(getTableName(standard.getInterface(type)),
+                                    null, asMap(value), stmt, new SQLBuilder(helper));
+                            if (dependency == null) {
+                                return null;                    // Dependency not found.
+                            }
+                            value = dependency;
+                        }
+                    }
+                }
+            }
+            /*
+             * Builds the SQL statement with the resolved value.
+             */
+            if (helper.isEmpty()) {
+                helper.append("SELECT ").append(ID_COLUMN).append(" FROM ")
+                        .appendIdentifier(schema, table).append(" WHERE ");
+            } else {
+                helper.append(" AND ");
+            }
+            helper.appendIdentifier(column).appendCondition(value);
+        }
+        /*
+         * The SQL statement is ready, with metadata dependency (if any) resolved. We can
now execute it.
+         * If more than one record is found, the identifier of the first one will be selected
add a warning
+         * will be logged.
+         */
+        String identifier = null;
+        try (ResultSet rs = stmt.executeQuery(helper.toString())) {
+            while (rs.next()) {
+                final String candidate = rs.getString(1);
+                if (candidate != null) {
+                    if (identifier == null) {
+                        identifier = candidate;
+                    } else if (!identifier.equals(candidate)) {
+                        warning(MetadataSource.class, "search", resources().getLogRecord(
+                                Level.WARNING, Errors.Keys.DuplicatedElement_1, candidate));
+                        break;
+                    }
+                }
+            }
+        }
+        return identifier;
+    }
+
+    /**
+     * Returns the set of all columns in a table, or an empty set if none (never {@code null}).
+     * Because each table should have at least the {@value #ID_COLUMN} column, an empty set
of
+     * columns will be understood as meaning that the table does not exist.
+     *
+     * <p>This method returns a direct reference to the cached set. The returned set
shall be
+     * modified in-place if new columns are added in the database table.</p>
+     *
+     * @param  table  the name of the table for which to get the columns.
+     * @return the set of columns, or an empty set if the table has not yet been created.
+     * @throws SQLException if an error occurred while querying the database.
+     */
+    final Set<String> getExistingColumns(final String table) throws SQLException {
+        assert Thread.holdsLock(statements);
+        Set<String> columns = tables.get(table);
+        if (columns == null) {
+            columns = new HashSet<>();
+            /*
+             * Note: a null schema in the DatabaseMetadata.getColumns(…) call means "do
not take schema in account";
+             * it does not mean "no schema" (the later is specified by an empty string).
This match better what we
+             * want because if we do not specify a schema in a SELECT statement, then the
actual schema used depends
+             * on the search path specified in the database environment variables.
+             */
+            try (ResultSet rs = statements.connection().getMetaData().getColumns(CATALOG,
schema, table, null)) {
+                while (rs.next()) {
+                    if (!columns.add(rs.getString("COLUMN_NAME"))) {
+                        // Paranoiac check, but should never happen.
+                        throw new SQLNonTransientException(table);
+                    }
+                }
+            }
+            tables.put(table, columns);
+        }
+        return columns;
+    }
+
+    /**
      * Temporary place-holder for a method to be developed later.
      */
     public <T> T lookup(final Class<T> type, String identifier) throws MetadataStoreException
{
@@ -284,4 +473,39 @@ public class MetadataSource {
         }
         return null;
     }
+
+    /**
+     * Returns the resources for warnings and error messages.
+     */
+    private static Errors resources() {
+        return Errors.getResources((Locale) null);
+    }
+
+    /**
+     * Reports a warning.
+     *
+     * @param source  the class to report as the warning emitter.
+     * @param method  the method to report as the warning emitter.
+     * @param record  the warning to report.
+     */
+    private void warning(final Class<?> source, final String method, final LogRecord
record) {
+        record.setSourceClassName(source.getCanonicalName());
+        record.setSourceMethodName(method);
+        record.setLoggerName(Loggers.SQL);
+        statements.listeners.warning(record);
+    }
+
+    /**
+     * Closes the database connection used by this object.
+     *
+     * @throws MetadataStoreException if an error occurred while closing the connection.
+     */
+    @Override
+    public void close() throws MetadataStoreException {
+        try {
+            statements.close();
+        } catch (SQLException e) {
+            throw new MetadataStoreException(e);
+        }
+    }
 }

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java?rev=1770513&r1=1770512&r2=1770513&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java
[UTF-8] Sat Nov 19 20:26:40 2016
@@ -45,6 +45,15 @@ public class MetadataStoreException exte
     }
 
     /**
+     * Creates an instance of {@code MetadataException} with the specified cause.
+     *
+     * @param cause  the cause of this exception.
+     */
+    public MetadataStoreException(final Exception cause) {
+        super(cause);
+    }
+
+    /**
      * Creates an instance of {@code MetadataException} with the specified message and cause.
      *
      * @param message  the detail message.

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ResultPool.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ResultPool.java?rev=1770513&r1=1770512&r2=1770513&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ResultPool.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ResultPool.java
[UTF-8] Sat Nov 19 20:26:40 2016
@@ -115,7 +115,7 @@ final class ResultPool {
     /**
      * Where to report the warnings. This is not necessarily a logger, since users can register
listeners.
      */
-    private final WarningListeners<MetadataSource> listeners;
+    final WarningListeners<MetadataSource> listeners;
 
     /**
      * A helper class used for constructing SQL statements.



Mime
View raw message