sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 19/33: SQL metadaat: fix an "ID column not found" error on PostgreSQL, more compact identifiers, and replace CRS WKT by EPSG codes.
Date Mon, 18 Jun 2018 21:02:41 GMT
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

commit f60046deb7beedfcc7ae73b5390de4b0386433e9
Author: Martin Desruisseaux <desruisseaux@apache.org>
AuthorDate: Sat Jun 2 17:50:14 2018 +0000

    SQL metadaat: fix an "ID column not found" error on PostgreSQL, more compact identifiers,
and replace CRS WKT by EPSG codes.
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sis/branches/JDK8@1832743 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sis/internal/metadata/ReferencingServices.java |  16 ++-
 .../sis/internal/metadata/sql/SQLBuilder.java      |  16 ++-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   6 +-
 .../apache/sis/metadata/iso/DefaultMetadata.java   |  16 ++-
 .../sis/metadata/iso/citation/Citations.java       |   2 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |  42 ++------
 .../apache/sis/metadata/sql/MetadataWriter.java    |  57 +++++++----
 .../apache/sis/metadata/sql/TableHierarchy.java    | 108 +++++++++++++++++++++
 .../internal/referencing/ServicesForMetadata.java  |  32 +++++-
 .../apache/sis/referencing/IdentifiedObjects.java  |  34 ++++---
 .../sis/referencing/datum/BursaWolfParameters.java |   2 +-
 .../operation/CoordinateOperationRegistry.java     |   6 +-
 .../java/org/apache/sis/util/CharSequences.java    |   2 +-
 13 files changed, 251 insertions(+), 88 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
index 40b5ec7..aed3092 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
@@ -67,7 +67,7 @@ import org.apache.sis.util.Deprecable;
  * <cite>"referencing by coordinates"</cite> but needed by metadata.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -286,6 +286,20 @@ public class ReferencingServices extends OptionalDependency {
         throw moduleNotFound();
     }
 
+    /**
+     * Returns an identifier for the given object, giving precedence to EPSG identifier if
available.
+     * The returned string should be of the form {@code "AUTHORITY:CODE"} if possible (no
guarantees).
+     *
+     * @param  object  the object for which to get an identifier.
+     * @return an identifier for the given object, with preference given to EPSG codes.
+     * @throws FactoryException if an error occurred while searching for the EPSG code.
+     *
+     * @since 1.0
+     */
+    public String getPreferredIdentifier(final IdentifiedObject object) throws FactoryException
{
+        throw moduleNotFound();
+    }
+
 
 
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
index 788129c..56661c5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
@@ -19,6 +19,9 @@ package org.apache.sis.internal.metadata.sql;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 import java.util.StringTokenizer;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.IdentifiedObject;
+import org.apache.sis.internal.metadata.ReferencingServices;
 
 
 /**
@@ -26,7 +29,7 @@ import java.util.StringTokenizer;
  * This class is for internal purpose only and may change or be removed in any future SIS
version.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -181,8 +184,9 @@ public class SQLBuilder {
      *
      * @param  value  the value to append, or {@code null}.
      * @return this builder, for method call chaining.
+     * @throws FactoryException if an error occurred while using the geodetic database.
      */
-    public final SQLBuilder appendCondition(final Object value) {
+    public final SQLBuilder appendCondition(final Object value) throws FactoryException {
         if (value == null) {
             buffer.append(" IS NULL");
             return this;
@@ -196,8 +200,9 @@ public class SQLBuilder {
      *
      * @param  value  the value to append, or {@code null}.
      * @return this builder, for method call chaining.
+     * @throws FactoryException if an error occurred while using the geodetic database.
      */
-    public final SQLBuilder appendValue(final Object value) {
+    public final SQLBuilder appendValue(Object value) throws FactoryException {
         if (value == null) {
             buffer.append("NULL");
         } else if (value instanceof Boolean) {
@@ -205,6 +210,9 @@ public class SQLBuilder {
         } else if (value instanceof Number) {
             buffer.append(value);
         } else {
+            if (value instanceof IdentifiedObject) {
+                value = ReferencingServices.getInstance().getPreferredIdentifier((IdentifiedObject)
value);
+            }
             buffer.append('\'').append(doubleQuotes(value)).append('\'');
         }
         return this;
@@ -297,7 +305,7 @@ public class SQLBuilder {
         final String name = buffer.append(table).append('_').append(column).append("_fkey").toString();
         return clear().append("ALTER TABLE ").appendIdentifier(schema, table).append(" ADD
CONSTRAINT ")
                 .appendIdentifier(name).append(" FOREIGN KEY(").appendIdentifier(column).append(")
REFERENCES ")
-                .appendIdentifier(schema, target).append(" (").append(primaryKey)
+                .appendIdentifier(schema, target).append(" (").appendIdentifier(primaryKey)
                 .append(") ON UPDATE ").append(cascade ? "CASCADE" : "RESTRICT")
                 .append(" ON DELETE RESTRICT").toString();
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
index 4c4c4cb..859311c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
@@ -354,6 +354,10 @@ class PropertyAccessor {
             Class<?> elementType = getter.getReturnType();
             if (Collection.class.isAssignableFrom(elementType)) {
                 elementType = Classes.boundOfParameterizedProperty(getter);
+                if (elementType == null) {
+                    // Subclass has erased parameterized type. Use method declared in the
interface.
+                    elementType = Classes.boundOfParameterizedProperty(getters[i]);
+                }
             }
             elementTypes[i] = Numbers.primitiveToWrapper(elementType);
         }
@@ -1238,7 +1242,7 @@ class PropertyAccessor {
         }
         Object copy = copier.copies.get(metadata);
         if (copy == null) {
-            copy = implementation.newInstance();
+            copy = implementation.getConstructor().newInstance();
             copier.copies.put(metadata, copy);              // Need to be first in case of
cyclic graphs.
             final Object[] arguments = new Object[1];
             for (int i=0; i<allCount; i++) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 4f0399b..8720ff6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -70,6 +70,7 @@ import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
 import org.apache.sis.internal.jaxb.LegacyNamespaces;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.jaxb.NonMarshalledAuthority;
 import org.apache.sis.internal.jaxb.metadata.CI_Citation;
 import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
 
@@ -191,11 +192,6 @@ public class DefaultMetadata extends ISOMetadata implements Metadata
{
     private static final long serialVersionUID = -4935599812744534502L;
 
     /**
-     * Unique identifier for this metadata record, or {@code null} if none.
-     */
-    private Identifier metadataIdentifier;
-
-    /**
      * Language(s) used for documenting metadata.
      */
     private Collection<Locale> languages;
@@ -347,7 +343,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
     public DefaultMetadata(final Metadata object) {
         super(object);
         if (object != null) {
-            metadataIdentifier            = object.getMetadataIdentifier();
+            identifiers                   = singleton(object.getMetadataIdentifier(), Identifier.class);
             parentMetadata                = object.getParentMetadata();
             languages                     = copyCollection(object.getLanguages(),       
             Locale.class);
             characterSets                 = copyCollection(object.getCharacterSets(),   
             Charset.class);
@@ -423,7 +419,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
     @XmlElement(name = "metadataIdentifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
     public Identifier getMetadataIdentifier() {
-        return metadataIdentifier;
+        return NonMarshalledAuthority.getMarshallable(identifiers);
     }
 
     /**
@@ -435,7 +431,8 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
      */
     public void setMetadataIdentifier(final Identifier newValue) {
         checkWritePermission();
-        metadataIdentifier = newValue;
+        identifiers = nonNullCollection(identifiers, Identifier.class);
+        NonMarshalledAuthority.setMarshallable(identifiers, newValue);
     }
 
     /**
@@ -467,7 +464,8 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
      */
     @Deprecated
     public void setFileIdentifier(final String newValue) {
-        DefaultIdentifier identifier = DefaultIdentifier.castOrCopy(metadataIdentifier);
// See "Note about deprecated methods implementation"
+        // See "Note about deprecated methods implementation"
+        DefaultIdentifier identifier = DefaultIdentifier.castOrCopy(NonMarshalledAuthority.getMarshallable(identifiers));
         if (identifier == null) {
             if (newValue == null) return;
             identifier = new DefaultIdentifier();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
index a890a6e..6eee404 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
@@ -684,7 +684,7 @@ public final class Citations extends Static {
      *         or {@code null} if the given citation is null or does not have any Unicode
identifier or title.
      *
      * @see org.apache.sis.metadata.iso.ImmutableIdentifier
-     * @see org.apache.sis.referencing.IdentifiedObjects#getUnicodeIdentifier(IdentifiedObject)
+     * @see org.apache.sis.referencing.IdentifiedObjects#getSimpleNameOrIdentifier(IdentifiedObject)
      * @see org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence)
      *
      * @since 0.6
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index e606ec1..e268e80 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@ -47,6 +47,7 @@ import java.sql.PreparedStatement;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
 import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.FactoryException;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.KeyNamePolicy;
 import org.apache.sis.metadata.ValueExistencePolicy;
@@ -125,16 +126,6 @@ public class MetadataSource implements AutoCloseable {
     static final String ID_COLUMN = "ID";
 
     /**
-     * Delimiter characters for the table name in identifier. Table names are prefixed to
identifiers only if
-     * the type represented by the table is a subtype. For example since {@code CI_Organisation}
is a subtype
-     * of {@code CI_Party}, identifiers for organizations need to be prefixed by {@code {CI_Organisation}}
in
-     * order allow {@code MetadataSource} to know in which table to search for such party.
-     *
-     * @see MetadataWriter#isReservedChar(int)
-     */
-    static final char TYPE_OPEN = '{', TYPE_CLOSE = '}';
-
-    /**
      * The timeout before to close a prepared statement, in nanoseconds. This is set to 2
seconds,
      * which is a bit short but should be okay if the {@link DataSource} creates pooled connections.
      * In case there is no connection pool, then the mechanism defined in this package will
hopefully
@@ -672,6 +663,8 @@ public class MetadataSource implements AutoCloseable {
                         identifier = search(table, null, asMap, stmt, helper());
                     } catch (SQLException e) {
                         throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
+                    } catch (FactoryException e) {
+                        throw new MetadataStoreException(e.getLocalizedMessage(), e);
                     }
                 }
             }
@@ -692,7 +685,7 @@ public class MetadataSource implements AutoCloseable {
      * @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
+            final Statement stmt, final SQLBuilder helper) throws SQLException, FactoryException
     {
         assert Thread.holdsLock(this);
         helper.clear();
@@ -743,7 +736,7 @@ public class MetadataSource implements AutoCloseable {
              * Builds the SQL statement with the resolved value.
              */
             if (helper.isEmpty()) {
-                helper.append("SELECT ").append(ID_COLUMN).append(" FROM ")
+                helper.append("SELECT ").appendIdentifier(ID_COLUMN).append(" FROM ")
                         .appendIdentifier(schema, table).append(" WHERE ");
             } else {
                 helper.append(" AND ");
@@ -811,25 +804,6 @@ public class MetadataSource implements AutoCloseable {
     }
 
     /**
-     * If the given identifier specifies a subtype of the given type, then returns that subtype.
-     * For example if the given type is {@code Party.class} and the given identifier is
-     * {@code "{CI_Organisation}EPSG"}, then this method returns {@code Organisation.class}.
-     * Otherwise this method returns {@code type} unchanged.
-     */
-    private static Class<?> subType(Class<?> type, final String identifier) {
-        if (identifier.charAt(0) == TYPE_OPEN) {
-            final int i = identifier.indexOf(TYPE_CLOSE);
-            if (i >= 0) {
-                final Class<?> subType = Types.forStandardName(identifier.substring(1,
i));
-                if (subType != null && type.isAssignableFrom(subType)) {
-                    type = subType;
-                }
-            }
-        }
-        return type;
-    }
-
-    /**
      * Returns an implementation of the specified metadata interface filled with the data
referenced
      * by the specified identifier. Alternatively, this method can also return a {@link CodeList}
or
      * {@link Enum} element.
@@ -871,7 +845,7 @@ public class MetadataSource implements AutoCloseable {
              */
             if (value == null) {
                 Method method = null;
-                final Class<?> subType = subType(type, identifier);
+                final Class<?> subType = TableHierarchy.subType(type, identifier);
                 final Dispatcher toSearch = new Dispatcher(identifier, this);
                 try {
                     value = subType.getConstructor().newInstance();
@@ -927,7 +901,7 @@ public class MetadataSource implements AutoCloseable {
          * If the identifier is prefixed with a table name as in "{CI_Organisation}identifier",
          * the name between bracket is a subtype of the given 'type' argument.
          */
-        final Class<?> type           = subType(info.getMetadataType(), toSearch.identifier);
+        final Class<?> type           = TableHierarchy.subType(info.getMetadataType(),
toSearch.identifier);
         final Class<?> returnType     = method.getReturnType();
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
         final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method)
: returnType;
@@ -951,7 +925,7 @@ public class MetadataSource implements AutoCloseable {
                     final SQLBuilder helper = helper();
                     final String query = helper.clear().append("SELECT * FROM ")
                             .appendIdentifier(schema, tableName).append(" WHERE ")
-                            .append(ID_COLUMN).append("=?").toString();
+                            .appendIdentifier(ID_COLUMN).append("=?").toString();
                     result = new CachedStatement(type, connection().prepareStatement(query),
listeners);
                 }
                 value = result.getValue(toSearch.identifier, columnName);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
index f7efec5..f205d0f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
@@ -22,6 +22,8 @@ import java.util.Iterator;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.IdentityHashMap;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.StringTokenizer;
 import java.util.logging.Level;
 import java.sql.Statement;
@@ -33,6 +35,7 @@ import java.lang.reflect.Modifier;
 
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.util.FactoryException;
 
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
@@ -48,6 +51,7 @@ import org.apache.sis.metadata.ValueExistencePolicy;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.metadata.sql.SQLBuilder;
+import org.apache.sis.xml.IdentifiedObject;
 
 // Branch-dependent imports
 import org.opengis.util.ControlledVocabulary;
@@ -106,7 +110,7 @@ import org.opengis.util.ControlledVocabulary;
  * </table>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -214,6 +218,8 @@ public class MetadataWriter extends MetadataSource {
              * little bit simpler, keep only the root cause provided that the exception type
is compatible.
              */
             throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
+        } catch (FactoryException e) {
+            throw new MetadataStoreException(e.getLocalizedMessage(), e);
         }
         return identifier;
     }
@@ -233,7 +239,7 @@ public class MetadataWriter extends MetadataSource {
      *         of the expected package.
      */
     private String add(final Statement stmt, final Object metadata, final Map<Object,String>
done,
-            final String parent) throws ClassCastException, SQLException
+            final String parent) throws ClassCastException, SQLException, FactoryException
     {
         final SQLBuilder helper = helper();
         /*
@@ -306,9 +312,9 @@ public class MetadataWriter extends MetadataSource {
                     }
                 }
                 /*
-                 * Determine the column data type. We infer that type from the method return
value, not from the
-                 * actual value for in the given metadata object, since the value type for
the same property may
-                 * be different in future calls to this method.
+                 * Determine the column data type. We infer that type from the method return
value, not from
+                 * actual value of given metadata object, since the value type for the same
property may be
+                 * different in future calls to this method.
                  */
                 int maxLength = maximumValueLength;
                 Class<?> rt = colTypes.get(column);
@@ -364,7 +370,7 @@ public class MetadataWriter extends MetadataSource {
          */
         final int minimalIdentifierLength;
         if (isChildTable) {
-            identifier = TYPE_OPEN + table + TYPE_CLOSE + identifier;
+            identifier = TableHierarchy.encode(table, identifier);
             minimalIdentifierLength = table.length() + 2;
         } else {
             minimalIdentifierLength = 0;
@@ -507,7 +513,7 @@ public class MetadataWriter extends MetadataSource {
         /*
          * Create the SQL statement which will insert the data.
          */
-        helper.clear().append("INSERT INTO ").appendIdentifier(schema(), table).append("
(").append(ID_COLUMN);
+        helper.clear().append("INSERT INTO ").appendIdentifier(schema(), table).append("
(").appendIdentifier(ID_COLUMN);
         for (final String column : asSingletons.keySet()) {
             helper.append(", ").appendIdentifier(column);
         }
@@ -601,7 +607,7 @@ public class MetadataWriter extends MetadataSource {
                                  * not inherited, then we have to repeat the primary key
creation in every child tables.
                                  */
                                 helper.append("(CONSTRAINT ").appendIdentifier(table + "_pkey")
-                                      .append(" PRIMARY KEY (").append(ID_COLUMN).append("))
");
+                                      .append(" PRIMARY KEY (").appendIdentifier(ID_COLUMN).append("))
");
                             }
                             inherits = new StringBuilder(helper.append(" INHERITS (").toString());
                         } else {
@@ -633,7 +639,7 @@ public class MetadataWriter extends MetadataSource {
      */
     private String createTable(final String table, final String primaryKey) throws SQLException
{
         return helper().clear().append("CREATE TABLE ").appendIdentifier(schema(), table)
-                .append(" (").append(primaryKey).append(" VARCHAR(").append(maximumIdentifierLength)
+                .append(" (").appendIdentifier(primaryKey).append(" VARCHAR(").append(maximumIdentifierLength)
                 .append(") NOT NULL PRIMARY KEY)").toString();
     }
 
@@ -644,7 +650,7 @@ public class MetadataWriter extends MetadataSource {
      * the native SQL {@code ENUM} type for making easier to add new values when a standard
      * is updated.
      */
-    private String addCode(final Statement stmt, final ControlledVocabulary code) throws
SQLException {
+    private String addCode(final Statement stmt, final ControlledVocabulary code) throws
SQLException, FactoryException {
         assert Thread.holdsLock(this);
         final String table = getTableName(code.getClass());
         final Set<String> columns = getExistingColumns(table);
@@ -653,16 +659,16 @@ public class MetadataWriter extends MetadataSource {
             columns.add(CODE_COLUMN);
         }
         final String identifier = Types.getCodeName(code);
-        final String query = helper().clear().append("SELECT ").append(CODE_COLUMN)
+        final String query = helper().clear().append("SELECT ").appendIdentifier(CODE_COLUMN)
                 .append(" FROM ").appendIdentifier(schema(), table).append(" WHERE ")
-                .append(CODE_COLUMN).appendCondition(identifier).toString();
+                .appendIdentifier(CODE_COLUMN).appendCondition(identifier).toString();
         final boolean exists;
         try (ResultSet rs = stmt.executeQuery(query)) {
             exists = rs.next();
         }
         if (!exists) {
             final String sql = helper().clear().append("INSERT INTO ").appendIdentifier(schema(),
table)
-                    .append(" (").append(CODE_COLUMN).append(") VALUES (").appendValue(identifier)
+                    .append(" (").appendIdentifier(CODE_COLUMN).append(") VALUES (").appendValue(identifier)
                     .append(')').toString();
             if (stmt.executeUpdate(sql) != 1) {
                 throw new SQLException(Errors.format(Errors.Keys.DatabaseUpdateFailure_3,
0, table, identifier));
@@ -674,8 +680,8 @@ public class MetadataWriter extends MetadataSource {
     /**
      * Suggests an identifier (primary key) to be used for the given metadata. This method
is invoked automatically
      * when a new metadata is about to be inserted in the database. The default implementation
uses heuristic rules
-     * of a few "well known" metadata like {@link Identifier} and {@link Citation}. Subclasses
can override this method
-     * for implementing their own heuristic.
+     * for a few "well known" metadata like {@link Identifier} and {@link Citation}. Subclasses
can override this
+     * method for implementing their own heuristic.
      *
      * <p>This method does not need to care about key collision.
      * The caller will adds some suffix if this is necessary for differentiating otherwise
identical identifiers.</p>
@@ -688,11 +694,22 @@ public class MetadataWriter extends MetadataSource {
      */
     protected String suggestIdentifier(final Object metadata, final Map<String,Object>
asValueMap) throws SQLException {
         String identifier = null;
+        final Collection<? extends Identifier> identifiers;
         if (metadata instanceof Identifier) {
-            identifier = nonEmpty(((Identifier) metadata).getCode());
-            final String cs = nonEmpty(((Identifier) metadata).getCodeSpace());
-            if (cs != null) {
-                identifier = (identifier != null) ? (cs + DefaultNameSpace.DEFAULT_SEPARATOR
+ identifier) : cs;
+            identifiers = Collections.singleton((Identifier) metadata);
+        } else if (metadata instanceof IdentifiedObject) {
+            identifiers = ((IdentifiedObject) metadata).getIdentifiers();
+        } else {
+            identifiers = Collections.emptySet();
+        }
+        for (final Identifier id : identifiers) {
+            identifier = nonEmpty(id.getCode());
+            if (identifier != null) {
+                final String cs = nonEmpty(id.getCodeSpace());
+                if (cs != null) {
+                    identifier = cs + DefaultNameSpace.DEFAULT_SEPARATOR + identifier;
+                }
+                break;
             }
         }
         if (identifier == null && metadata instanceof Citation) {
@@ -769,7 +786,7 @@ public class MetadataWriter extends MetadataSource {
      * Returns {@code true} if the given code point is a reserved character.
      */
     private static boolean isReservedChar(final int c) {
-        return (c == TYPE_OPEN) || (c == TYPE_CLOSE);
+        return (c == TableHierarchy.TYPE_OPEN) || (c == TableHierarchy.TYPE_CLOSE);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
new file mode 100644
index 0000000..41374f2
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.sis.util.iso.Types;
+
+
+/**
+ * Utility methods for handling the inheritance between tables.
+ * This features is partially supported in PostgreSQL database.
+ *
+ * @author desruisseaux
+ */
+final class TableHierarchy {
+    /**
+     * Delimiter characters for the table name in identifier. Table names are prefixed to
identifiers only if
+     * the type represented by the table is a subtype. For example since {@code CI_Organisation}
is a subtype
+     * of {@code CI_Party}, identifiers for organizations need to be prefixed by {@code {CI_Organisation}}
in
+     * order allow {@code MetadataSource} to know in which table to search for such party.
+     *
+     * @see MetadataWriter#isReservedChar(int)
+     */
+    static final char TYPE_OPEN = '{', TYPE_CLOSE = '}';
+
+    /**
+     * Abbreviations for commonly-used tables. We use those abbreviations because table names
like
+     * {@code "MD_VectorSpatialRepresentation"} consume a lot of space, which leave few spaces
left
+     * for actual identifiers when we want to limit the length to a relatively small value.
+     */
+    private static final Map<String,String> ABBREVIATIONS = new HashMap<>(16);
+
+    /**
+     * The reverse of {@link #ABBREVIATIONS}.
+     */
+    private static final Map<String,String> TABLES = new HashMap<>(16);
+    static {
+        add("MD_VectorSpatialRepresentation", "vec");
+        add("MD_GridSpatialRepresentation",   "grd");
+        add("MD_Georectified",                "rtf");
+        add("MD_Georeferenceable",            "cbl");
+        add("MD_DataIdentification",          "dat");
+        add("SV_ServiceIdentification",       "srv");
+        add("MD_FeatureCatalogueDescription", "cat");
+        add("MD_CoverageDescription",         "cov");
+        add("MD_ImageDescription",            "img");
+        add("MD_SampleDimension",             "sd");
+        add("MD_Band",                        "bd");
+    }
+
+    /**
+     * Adds an abbreviation. For class initialization only.
+     */
+    private static void add(final String table, final String abbreviation) {
+        ABBREVIATIONS.put(table, abbreviation);
+        TABLES.put(abbreviation, table);
+    }
+
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private TableHierarchy() {
+    }
+
+    /**
+     * Encode table name in the given identifier.
+     */
+    static String encode(String table, final String identifier) {
+        table = ABBREVIATIONS.getOrDefault(table, table);
+        return TYPE_OPEN + table + TYPE_CLOSE + identifier;
+    }
+
+    /**
+     * If the given identifier specifies a subtype of the given type, then returns that subtype.
+     * For example if the given type is {@code Party.class} and the given identifier is
+     * {@code "{CI_Organisation}EPSG"}, then this method returns {@code Organisation.class}.
+     * Otherwise this method returns {@code type} unchanged.
+     */
+    static Class<?> subType(Class<?> type, final String identifier) {
+        if (identifier.charAt(0) == TYPE_OPEN) {
+            final int i = identifier.indexOf(TYPE_CLOSE);
+            if (i >= 0) {
+                String table = identifier.substring(1, i);
+                table = TABLES.getOrDefault(table, table);
+                final Class<?> subType = Types.forStandardName(table);
+                if (subType != null && type.isAssignableFrom(subType)) {
+                    type = subType;
+                }
+            }
+        }
+        return type;
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
index 2fb197f..9a2000d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
@@ -53,6 +53,7 @@ import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.SingleOperation;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.citation.OnlineResource;
@@ -97,13 +98,14 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.util.iso.DefaultNameSpace;
 
 
 /**
  * Implements the referencing services needed by the {@code "sis-metadata"} module.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.5
  * @module
  */
@@ -411,6 +413,34 @@ public final class ServicesForMetadata extends ReferencingServices {
         return new DirectPosition2D(CommonCRS.defaultGeographic(), λ, φ);
     }
 
+    /**
+     * Returns an identifier for the given object, giving precedence to EPSG identifier if
available.
+     * The returned string should be of the form {@code "AUTHORITY:CODE"} if possible (no
guarantees).
+     *
+     * @param  object  the object for which to get an identifier.
+     * @return an identifier for the given object, with preference given to EPSG codes.
+     * @throws FactoryException if an error occurred while searching for the EPSG code.
+     *
+     * @since 1.0
+     */
+    @Override
+    public String getPreferredIdentifier(final IdentifiedObject object) throws FactoryException
{
+        final Integer code = IdentifiedObjects.lookupEPSG(object);
+        if (code != null) {
+            return Constants.EPSG + DefaultNameSpace.DEFAULT_SEPARATOR + code;
+        }
+        /*
+         * If above code did not found an EPSG code, discard EPSG codes that
+         * we may find in the loop below because they are probably invalid.
+         */
+        for (final Identifier id : object.getIdentifiers()) {
+            if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
+                return IdentifiedObjects.toString(id);
+            }
+        }
+        return IdentifiedObjects.getSimpleNameOrIdentifier(object);
+    }
+
 
 
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
index 0f0eec6..24f2e27 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
@@ -57,7 +57,7 @@ import static org.apache.sis.internal.util.Citations.identifierMatches;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see CRS
  * @see org.apache.sis.geometry.Envelopes
@@ -283,7 +283,7 @@ public final class IdentifiedObjects extends Static {
             final Iterator<? extends Identifier> it = iterator(object.getIdentifiers());
             if (it != null) while (it.hasNext()) {
                 final Identifier identifier = it.next();
-                if (identifier != null) { // Paranoiac check.
+                if (identifier != null) {                           // Paranoiac check.
                     if (authority == null || identifierMatches(authority, identifier.getAuthority()))
{
                         return identifier;
                     }
@@ -319,12 +319,12 @@ public final class IdentifiedObjects extends Static {
             final Iterator<? extends Identifier> it = iterator(object.getIdentifiers());
             if (it != null) while (it.hasNext()) {
                 final String code = toString(it.next());
-                if (code != null) { // Paranoiac check.
+                if (code != null) {                                 // Paranoiac check.
                     return code;
                 }
             }
             final String name = toString(object.getName());
-            if (name != null) { // Paranoiac check.
+            if (name != null) {                                     // Paranoiac check.
                 return name;
             }
         }
@@ -332,8 +332,8 @@ public final class IdentifiedObjects extends Static {
     }
 
     /**
-     * Returns the first name, alias or identifier which is a
-     * {@linkplain CharSequences#isUnicodeIdentifier(CharSequence) valid Unicode identifier}.
+     * Returns the first name, alias or identifier which is a valid Unicode identifier. This
method considers a
+     * name or identifier as valid if {@link CharSequences#isUnicodeIdentifier(CharSequence)}
returns {@code true}.
      * This method performs the search in the following order:
      *
      * <ul>
@@ -346,13 +346,15 @@ public final class IdentifiedObjects extends Static {
      * @return the first name, alias or identifier which is a valid Unicode identifier, or
{@code null} if none.
      *
      * @see org.apache.sis.metadata.iso.ImmutableIdentifier
-     * @see org.apache.sis.metadata.iso.citation.Citations#getUnicodeIdentifier(Citation)
-     * @see org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence)
+     * @see Citations#getUnicodeIdentifier(Citation)
+     * @see CharSequences#isUnicodeIdentifier(CharSequence)
+     *
+     * @since 1.0
      */
-    public static String getUnicodeIdentifier(final IdentifiedObject object) {
+    public static String getSimpleNameOrIdentifier(final IdentifiedObject object) {
         if (object != null) {
             Identifier identifier = object.getName();
-            if (identifier != null) { // Paranoiac check.
+            if (identifier != null) {                               // Paranoiac check.
                 final String code = identifier.getCode();
                 if (CharSequences.isUnicodeIdentifier(code)) {
                     return code;
@@ -371,7 +373,7 @@ public final class IdentifiedObjects extends Static {
             final Iterator<? extends Identifier> id = iterator(object.getIdentifiers());
             if (id != null) while (id.hasNext()) {
                 identifier = id.next();
-                if (identifier != null) { // Paranoiac check.
+                if (identifier != null) {                           // Paranoiac check.
                     final String code = identifier.getCode();
                     if (CharSequences.isUnicodeIdentifier(code)) {
                         return code;
@@ -383,6 +385,14 @@ public final class IdentifiedObjects extends Static {
     }
 
     /**
+     * @deprecated Renamed {@link #getSimpleNameOrIdentifier(IdentifiedObject)}.
+     */
+    @Deprecated
+    public static String getUnicodeIdentifier(final IdentifiedObject object) {
+        return getSimpleNameOrIdentifier(object);
+    }
+
+    /**
      * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:9.1:4326"}, of the specified
object.
      * This method searches in all {@linkplain org.apache.sis.referencing.factory.GeodeticAuthorityFactory
geodetic
      * authority factories} known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATIVE
@@ -764,7 +774,7 @@ public final class IdentifiedObjects extends Static {
         final String code = identifier.getCode();
         String cs = identifier.getCodeSpace();
         if (cs == null || cs.isEmpty()) {
-            cs = org.apache.sis.internal.util.Citations.getIdentifier(identifier.getAuthority(),
true);
+            cs = Citations.getUnicodeIdentifier(identifier.getAuthority());
         }
         if (cs != null) {
             return cs + DefaultNameSpace.DEFAULT_SEPARATOR + code;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
index 8dad6f7..21ec81a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
@@ -698,7 +698,7 @@ public class BursaWolfParameters extends FormattableObject implements
Cloneable,
             return WKTKeywords.ToWGS84;
         }
         formatter.setInvalidWKT(BursaWolfParameters.class, null);
-        String name = IdentifiedObjects.getUnicodeIdentifier(getTargetDatum());
+        String name = IdentifiedObjects.getSimpleNameOrIdentifier(getTargetDatum());
         if (name == null) {
             name = "Unknown";
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 67278e5..dedb2eb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -59,6 +59,7 @@ import org.apache.sis.referencing.factory.MissingFactoryResourceException;
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.DeferredCoordinateOperation;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
@@ -68,7 +69,6 @@ import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.system.Semaphores;
 import org.apache.sis.internal.system.Loggers;
-import org.apache.sis.internal.util.Citations;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
@@ -148,7 +148,7 @@ class CoordinateOperationRegistry {
      * Creates an identifier in the Apache SIS namespace for the given vocabulary key.
      */
     private static Identifier createIdentifier(final short key) {
-        return new NamedIdentifier(org.apache.sis.metadata.iso.citation.Citations.SIS, Vocabulary.formatInternational(key));
+        return new NamedIdentifier(Citations.SIS, Vocabulary.formatInternational(key));
     }
 
     /**
@@ -227,7 +227,7 @@ class CoordinateOperationRegistry {
             if (registry instanceof GeodeticAuthorityFactory) {
                 codeFinder = ((GeodeticAuthorityFactory) registry).newIdentifiedObjectFinder();
             } else try {
-                codeFinder = IdentifiedObjects.newFinder(Citations.getIdentifier(registry.getAuthority(),
false));
+                codeFinder = IdentifiedObjects.newFinder(Citations.getIdentifier(registry.getAuthority()));
             } catch (NoSuchAuthorityFactoryException e) {
                 Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
                         CoordinateOperationRegistry.class, "<init>", e);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/CharSequences.java b/core/sis-utility/src/main/java/org/apache/sis/util/CharSequences.java
index ea9a334..7090f34 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/CharSequences.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/CharSequences.java
@@ -1538,7 +1538,7 @@ cmp:    while (ia < lga) {
      *
      * @see org.apache.sis.metadata.iso.ImmutableIdentifier
      * @see org.apache.sis.metadata.iso.citation.Citations#getUnicodeIdentifier(Citation)
-     * @see org.apache.sis.referencing.IdentifiedObjects#getUnicodeIdentifier(IdentifiedObject)
+     * @see org.apache.sis.referencing.IdentifiedObjects#getSimpleNameOrIdentifier(IdentifiedObject)
      */
     public static boolean isUnicodeIdentifier(final CharSequence identifier) {
         final int length = length(identifier);


Mime
View raw message