You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/05/08 00:11:15 UTC

svn commit: r1794274 - in /sis/branches/JDK8: ./ core/sis-metadata/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/ core/sis-metadata/src/test/java/org/apache/sis/intern...

Author: desruisseaux
Date: Mon May  8 00:11:14 2017
New Revision: 1794274

URL: http://svn.apache.org/viewvc?rev=1794274&view=rev
Log:
Initial port of MetadataWriter, not yet fully enabled.

Added:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java   (with props)
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java   (with props)
Modified:
    sis/branches/JDK8/core/sis-metadata/pom.xml
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/IdentifierGeneratorTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
    sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties
    sis/branches/JDK8/pom.xml

Modified: sis/branches/JDK8/core/sis-metadata/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/pom.xml?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/pom.xml (original)
+++ sis/branches/JDK8/core/sis-metadata/pom.xml Mon May  8 00:11:14 2017
@@ -146,6 +146,11 @@ Implementations of metadata derived from
       <type>test-jar</type>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
 </project>

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java [UTF-8] Mon May  8 00:11:14 2017
@@ -26,38 +26,38 @@ import org.apache.sis.util.CharSequences
  * that can not (to our knowledge) be inferred from the {@link DatabaseMetaData}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.7
  * @module
  */
-enum Dialect {
+public enum Dialect {
     /**
      * The database is presumed to use ANSI SQL syntax.
      */
-    ANSI(null),
+    ANSI(null, false),
 
     /**
      * The database uses Derby syntax. This is ANSI, with some constraints that PostgreSQL does not have
      * (for example column with {@code UNIQUE} constraint must explicitly be specified as {@code NOT NULL}).
      */
-    DERBY("derby"),
+    DERBY("derby", false),
 
     /**
      * The database uses HSQL syntax. This is ANSI, but does not allow {@code INSERT} statements inserting many lines.
      * It also have a {@code SHUTDOWN} command which is specific to HSQLDB.
      */
-    HSQL("hsqldb"),
+    HSQL("hsqldb", false),
 
     /**
      * The database uses PostgreSQL syntax. This is ANSI, but provided an a separated
      * enumeration value because it allows a few additional commands like {@code VACUUM}.
      */
-    POSTGRESQL("postgresql"),
+    POSTGRESQL("postgresql", true),
 
     /**
      * The database uses Oracle syntax. This is ANSI, but without {@code "AS"} keyword.
      */
-    ORACLE("oracle");
+    ORACLE("oracle", false);
 
     /**
      * The protocol in JDBC URL, or {@code null} if unknown.
@@ -66,10 +66,24 @@ enum Dialect {
     private final String protocol;
 
     /**
+     * Whether this dialect support table inheritance.
+     */
+    public final boolean isTableInheritanceSupported;
+
+    /**
+     * {@code true} if child tables inherit the index of their parent tables.
+     * This feature is not yet supported in PostgreSQL.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-358">SIS-358</a>
+     */
+    public final boolean isIndexInheritanceSupported = false;
+
+    /**
      * Creates a new enumeration value for a SQL dialect for the given protocol.
      */
-    private Dialect(final String protocol) {
+    private Dialect(final String protocol, final boolean isTableInheritanceSupported) {
         this.protocol = protocol;
+        this.isTableInheritanceSupported = isTableInheritanceSupported;
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java [UTF-8] Mon May  8 00:11:14 2017
@@ -34,7 +34,7 @@ public class SQLBuilder {
     /**
      * The database dialect. This is used for a few database-dependent syntax.
      */
-    private final Dialect dialect;
+    public final Dialect dialect;
 
     /**
      * The characters used for quoting identifiers, or an empty string if none.

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=1794274&r1=1794273&r2=1794274&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] Mon May  8 00:11:14 2017
@@ -57,6 +57,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.collection.CodeListSet;
 import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.logging.WarningListeners;
@@ -85,6 +86,19 @@ import org.apache.sis.util.iso.Types;
  *
  * where {@code id} is the primary key value for the desired record in the {@code MD_Format} table.
  *
+ * <div class="section">Properties</div>
+ * The constructor expects three Java arguments (the {@linkplain MetadataStandard metadata standard},
+ * the {@linkplain DataSource data source} and the database schema) completed by an arbitrary amount
+ * of optional arguments given as a map of properties.
+ * The following keys are recognized by {@code MetadataSource} and all other entries are ignored:
+ *
+ * <table class="sis">
+ *   <caption>Optional properties at construction time</caption>
+ *   <tr><th>Key</th>                     <th>Value type</th>          <th>Description</th></tr>
+ *   <tr><td>{@code "classloader"}</td>   <td>{@link ClassLoader}</td> <td>The class loader to use for creating {@link Proxy} instances.</td></tr>
+ *   <tr><td>{@code "maxStatements"}</td> <td>{@link Integer}</td>     <td>Maximal number of {@link PreparedStatement}s that can be kept simultaneously open.</td></tr>
+ * </table>
+ *
  * <div class="section">Concurrency</div>
  * {@code MetadataSource} is thread-safe but is not concurrent. If concurrency is desired,
  * multiple instances of {@code MetadataSource} can be created for the same {@link DataSource}.
@@ -101,13 +115,13 @@ public class MetadataSource implements A
      * The catalog, set to {@code null} for now. This is defined as a constant in order to make easier
      * to spot the places where catalog would be used, if we want to use it in a future version.
      */
-    private static final String CATALOG = null;
+    static final String CATALOG = null;
 
     /**
      * The column name used for the identifiers. We do not quote this identifier;
      * we will let the database uses its own lower-case / upper-case convention.
      */
-    private static final String ID_COLUMN = "ID";
+    static final String ID_COLUMN = "ID";
 
     /**
      * The timeout before to close a prepared statement, in nanoseconds. This is set to 2 seconds,
@@ -293,7 +307,7 @@ public class MetadataSource implements A
             synchronized (MetadataSource.class) {
                 ms = instance;
                 if (ms == null) {
-                    ms = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null, null);
+                    ms = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null);
                     ms.install();
                     instance = ms;
                 }
@@ -308,18 +322,18 @@ public class MetadataSource implements A
      * the database source are mandatory information.
      * All other information are optional and can be {@code null}.
      *
-     * @param  standard       the metadata standard to implement.
-     * @param  dataSource     the source for getting a connection to the database.
-     * @param  schema         the database schema were metadata tables are stored, or {@code null} if none.
-     * @param  classloader    the class loader to use for creating {@link Proxy} instances, or {@code null} for the default.
-     * @param  maxStatements  maximal number of {@link PreparedStatement}s that can be kept simultaneously open,
-     *                        or {@code null} for a default value.
+     * @param  standard    the metadata standard to implement.
+     * @param  dataSource  the source for getting a connection to the database.
+     * @param  schema      the database schema were metadata tables are stored, or {@code null} if none.
+     * @param  properties  additional options, or {@code null} if none. See class javadoc for a description.
      */
     public MetadataSource(final MetadataStandard standard, final DataSource dataSource,
-            final String schema, ClassLoader classloader, Integer maxStatements)
+            final String schema, final Map<String,?> properties)
     {
         ArgumentChecks.ensureNonNull("standard",   standard);
         ArgumentChecks.ensureNonNull("dataSource", dataSource);
+        ClassLoader classloader   = Containers.property(properties, "classloader",   ClassLoader.class);
+        Integer     maxStatements = Containers.property(properties, "maxStatements", Integer.class);
         if (classloader == null) {
             classloader = getClass().getClassLoader();
         }
@@ -425,9 +439,16 @@ public class MetadataSource implements A
     }
 
     /**
+     * Returns the database schema where metadata are stored, or {@code null} if none.
+     */
+    final String schema() {
+        return schema;
+    }
+
+    /**
      * Returns a helper class for building SQL statements.
      */
-    private SQLBuilder helper() throws SQLException {
+    final SQLBuilder helper() throws SQLException {
         assert Thread.holdsLock(this);
         if (helper == null) {
             helper = new SQLBuilder(connection().getMetaData(), quoteSchema);
@@ -503,7 +524,7 @@ public class MetadataSource implements A
      * Returns the table name for the specified class.
      * This is usually the ISO 19115 name.
      */
-    private static String getTableName(final Class<?> type) {
+    static String getTableName(Class<?> type) {
         final UML annotation = type.getAnnotation(UML.class);
         if (annotation == null) {
             return type.getSimpleName();
@@ -531,7 +552,7 @@ public class MetadataSource implements A
      * @param  metadata  the metadata to test.
      * @return the identifier (primary key), or {@code null} if the given metadata is not a proxy.
      */
-    private String proxy(final Object metadata) {
+    final String proxy(final Object metadata) {
         return (metadata instanceof MetadataProxy) ? ((MetadataProxy) metadata).identifier(this) : null;
     }
 
@@ -545,7 +566,7 @@ public class MetadataSource implements A
      * @throws ClassCastException if the metadata object does not implement a metadata interface
      *         of the expected package.
      */
-    private Map<String,Object> asMap(final Object metadata) throws ClassCastException {
+    final Map<String,Object> asValueMap(final Object metadata) throws ClassCastException {
         return standard.asValueMap(metadata, null, KeyNamePolicy.UML_IDENTIFIER, ValueExistencePolicy.ALL);
     }
 
@@ -557,7 +578,7 @@ public class MetadataSource implements A
      * @return the given value, or its first element if the value is a collection,
      *         or {@code null} if the given value is null or an empty collection.
      */
-    private static Object extractFromCollection(Object value) {
+    static Object extractFromCollection(Object value) {
         while (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
             if (!it.hasNext()) {
@@ -593,7 +614,7 @@ public class MetadataSource implements A
                 final Map<String,Object> asMap;
                 try {
                     table = getTableName(standard.getInterface(metadata.getClass()));
-                    asMap = asMap(metadata);
+                    asMap = asValueMap(metadata);
                 } catch (ClassCastException e) {
                     throw new MetadataStoreException(Errors.format(
                             Errors.Keys.IllegalArgumentClass_2, "metadata", metadata.getClass()));
@@ -602,7 +623,7 @@ public class MetadataSource implements A
                     try (Statement stmt = connection().createStatement()) {
                         identifier = search(table, null, asMap, stmt, helper());
                     } catch (SQLException e) {
-                        throw new MetadataStoreException(e);
+                        throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
                     }
                 }
             }
@@ -622,7 +643,7 @@ public class MetadataSource implements A
      * @return the identifier of the given metadata, or {@code null} if none.
      * @throws SQLException if an error occurred while searching in the database.
      */
-    private String search(final String table, Set<String> columns, final Map<String,Object> metadata,
+    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(this);
@@ -659,7 +680,7 @@ public class MetadataSource implements A
                         final Class<?> type = value.getClass();
                         if (standard.isMetadata(type)) {
                             dependency = search(getTableName(standard.getInterface(type)),
-                                    null, asMap(value), stmt, new SQLBuilder(helper));
+                                    null, asValueMap(value), stmt, new SQLBuilder(helper));
                             if (dependency == null) {
                                 return null;                    // Dependency not found.
                             }
@@ -692,7 +713,7 @@ public class MetadataSource implements A
                     if (identifier == null) {
                         identifier = candidate;
                     } else if (!identifier.equals(candidate)) {
-                        warning("search", resources().getLogRecord(
+                        warning(MetadataSource.class, "search", Errors.getResources((Locale) null).getLogRecord(
                                 Level.WARNING, Errors.Keys.DuplicatedElement_1, candidate));
                         break;
                     }
@@ -714,7 +735,7 @@ public class MetadataSource implements A
      * @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.
      */
-    private Set<String> getExistingColumns(final String table) throws SQLException {
+    final Set<String> getExistingColumns(final String table) throws SQLException {
         assert Thread.holdsLock(this);
         Set<String> columns = tableColumns.get(table);
         if (columns == null) {
@@ -964,20 +985,14 @@ public class MetadataSource implements A
     }
 
     /**
-     * Returns the resources for warnings and error messages.
-     */
-    private static Errors resources() {
-        return Errors.getResources((Locale) null);
-    }
-
-    /**
      * Reports a warning.
      *
+     * @param source  the source class, either {@code MetadataSource} or {@code MetadataWriter}.
      * @param method  the method to report as the warning emitter.
      * @param record  the warning to report.
      */
-    private void warning(final String method, final LogRecord record) {
-        record.setSourceClassName(MetadataSource.class.getCanonicalName());
+    final void warning(final Class<? extends MetadataSource> source, final String method, final LogRecord record) {
+        record.setSourceClassName(source.getCanonicalName());
         record.setSourceMethodName(method);
         record.setLoggerName(Loggers.SQL);
         listeners.warning(record);
@@ -1112,7 +1127,7 @@ public class MetadataSource implements A
              */
             final LogRecord record = new LogRecord(Level.WARNING, e.toString());
             record.setThrown(e);
-            warning("closeExpired", record);
+            warning(MetadataSource.class, "closeExpired", record);
         }
     }
 
@@ -1137,7 +1152,7 @@ public class MetadataSource implements A
             }
             helper = null;
         } catch (SQLException e) {
-            throw new MetadataStoreException(e);
+            throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
         }
     }
 }

Added: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java?rev=1794274&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java [UTF-8] Mon May  8 00:11:14 2017
@@ -0,0 +1,677 @@
+/*
+ * 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.Set;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.IdentityHashMap;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.sql.Statement;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+import org.opengis.util.CodeList;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+
+import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Messages;
+import org.apache.sis.util.iso.DefaultNameSpace;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.metadata.KeyNamePolicy;
+import org.apache.sis.metadata.TypeValuePolicy;
+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;
+
+
+/**
+ * A connection to a metadata database with write capabilities. The database must have a schema of the given name,
+ * which can be initially empty. Tables and columns are created as needed when the {@link #add(Object)} method is
+ * invoked.
+ *
+ * <p>No more than one instance of {@code MetadataWriter} should be used for the same database.
+ * However multiple instances of {@code MetadataSource} can be used concurrently with a single
+ * {@code MetadataWriter} instance on the same database.</p>
+ *
+ * <div class="section">Properties</div>
+ * The constructor expects three Java arguments (the {@linkplain MetadataStandard metadata standard},
+ * the {@linkplain DataSource data source} and the database schema) completed by an arbitrary amount
+ * of optional arguments given as a map of properties.
+ * The following keys are recognized by {@code MetadataSource} and all other entries are ignored:
+ *
+ * <table class="sis">
+ *   <caption>Optional properties at construction time</caption>
+ *   <tr>
+ *     <th>Key</th>
+ *     <th>Value type</th>
+ *     <th>Description</th>
+ *   </tr><tr>
+ *     <td>{@code "classloader"}</td>
+ *     <td>{@link ClassLoader}</td>
+ *     <td>The class loader to use for creating {@link java.lang.reflect.Proxy} instances.</td>
+ *   </tr><tr>
+ *     <td>{@code "maxStatements"}</td>
+ *     <td>{@link Integer}</td>
+ *     <td>Maximal number of {@link java.sql.PreparedStatement}s that can be kept simultaneously open.</td>
+ *   </tr><tr>
+ *     <td>{@code "maximumIdentifierLength"}</td>
+ *     <td>{@link Integer}</td>
+ *     <td>The maximal number of characters allowed for primary keys.
+ *         This is the value given to the {@code VARCHAR} type when creating new {@code "ID"} columns.</td>
+ *   </tr><tr>
+ *     <td>{@code "maximumValueLength"}</td>
+ *     <td>{@link Integer}</td>
+ *     <td>Maximal number of characters allowed in text columns. This is the parameter given to the {@code VARCHAR}
+ *         type when creating new columns. Attempts to insert a text longer than this limit will typically throws
+ *         a {@link SQLException}, but the exact behavior is database-dependent.</td>
+ *   </tr><tr>
+ *       <td>{@code "columnCreationPolicy"}</td>
+ *       <td>{@link ValueExistencePolicy}</td>
+ *       <td>Whether columns should be created only for non-empty attributes ({@link ValueExistencePolicy#NON_EMPTY
+ *           NON-EMPTY}, the default) or for all attributes ({@link ValueExistencePolicy#ALL ALL})</td>
+ *   </tr>
+ * </table>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public class MetadataWriter extends MetadataSource {
+    /**
+     * The name of the column for code list.
+     */
+    private static final String CODE_COLUMN = "CODE";
+
+    /**
+     * Minimum value allowed for {@link #maximumIdentifierLength}.
+     */
+    private static final int MINIMAL_LIMIT = 5;
+
+    /**
+     * Maximal length for the identifier. This applies also to code list values.
+     */
+    private final int maximumIdentifierLength;
+
+    /**
+     * Maximal length of values.
+     */
+    private final int maximumValueLength;
+
+    /**
+     * Whether the tables should contain a column for every attribute, or only for non-null
+     * and non-empty attributes. The default is {@link ValueExistencePolicy#NON_EMPTY NON-EMPTY}.
+     */
+    private final ValueExistencePolicy columnCreationPolicy;
+
+    /**
+     * Creates a new metadata writer.
+     *
+     * @param  standard    the metadata standard to implement.
+     * @param  dataSource  the source for getting a connection to the database.
+     * @param  schema      the database schema were metadata tables are stored, or {@code null} if none.
+     * @param  properties  additional options, or {@code null} if none. See class javadoc for a description.
+     */
+    public MetadataWriter(final MetadataStandard standard, final DataSource dataSource, final String schema,
+            final Map<String,?> properties)
+    {
+        super(standard, dataSource, schema, properties);
+        Integer maximumIdentifierLength           = Containers.property(properties, "maximumIdentifierLength", Integer.class);
+        Integer maximumValueLength                = Containers.property(properties, "maximumValueLength",      Integer.class);
+        ValueExistencePolicy columnCreationPolicy = Containers.property(properties, "columnCreationPolicy",    ValueExistencePolicy.class);
+        if (maximumIdentifierLength != null) {
+            ArgumentChecks.ensureBetween("maximumIdentifierLength", MINIMAL_LIMIT, 100, maximumIdentifierLength);
+            this.maximumIdentifierLength = maximumIdentifierLength;
+        } else {
+            this.maximumIdentifierLength = 24;
+        }
+        if (maximumValueLength != null) {
+            ArgumentChecks.ensureBetween("maximumValueLength", MINIMAL_LIMIT, Short.MAX_VALUE, maximumValueLength);
+            this.maximumValueLength = maximumValueLength;
+        } else {
+            this.maximumValueLength = 1000;
+        }
+        this.columnCreationPolicy = (columnCreationPolicy != null) ? columnCreationPolicy : ValueExistencePolicy.NON_EMPTY;
+    }
+
+    /**
+     * Adds the given metadata object to the database, if it does not already exists.
+     * If the database already contains a metadata equals to the given one, then the
+     * database is left unchanged and the identifier of the existing metadata is returned.
+     *
+     * @param  metadata  the metadata object to add.
+     * @return the identifier (primary key) of the metadata just added,
+     *         or the identifier of the existing metadata is one exists.
+     * @throws MetadataStoreException if the metadata object does not implement a metadata interface
+     *         of the expected package, if an exception occurred while reading or writing the database.
+     *         In such case, the database content is left unchanged
+     *         (i.e. this method is a <cite>all or nothing</cite> operation).
+     */
+    public String add(final Object metadata) throws MetadataStoreException {
+        String identifier = proxy(metadata);
+        if (identifier == null) try {
+            synchronized (this) {
+                final Connection connection = connection();
+                connection.setAutoCommit(false);
+                boolean success = false;
+                try {
+                    try (Statement stmt = connection.createStatement()) {
+                        if (metadata instanceof CodeList<?>) {
+                            identifier = addCode(stmt, (CodeList<?>) metadata);
+                        } else {
+                            identifier = add(stmt, metadata, new IdentityHashMap<>(), null);
+                        }
+                    }
+                    success = true;
+                } finally {
+                    if (success) {
+                        connection.commit();
+                    } else {
+                        connection.rollback();
+                    }
+                    connection.setAutoCommit(true);
+                }
+            }
+        } catch (ClassCastException e) {
+            throw new MetadataStoreException(Errors.format(
+                    Errors.Keys.IllegalArgumentClass_2, "metadata", metadata.getClass()));
+        } catch (SQLException e) {
+            /*
+             * Derby sometime wraps SQLException into another SQLException.  For making the stack strace a
+             * little bit simpler, keep only the root cause provided that the exception type is compatible.
+             */
+            throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
+        }
+        return identifier;
+    }
+
+    /**
+     * Implementation of the {@link #add(Object)} method. This method invokes itself recursively,
+     * and maintains a map of metadata inserted up to date in order to avoid infinite recursivity.
+     *
+     * @param  stmt      the statement to use for inserting data.
+     * @param  metadata  the metadata object to add.
+     * @param  done      the metadata objects already added, mapped to their primary keys.
+     * @param  parent    the primary key of the parent, or {@code null} if there is no parent.
+     * @return the identifier (primary key) of the metadata just added.
+     * @throws SQLException if an exception occurred while reading or writing the database.
+     * @throws ClassCastException if the metadata object does not implement a metadata interface
+     *         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 SQLBuilder helper = helper();
+        /*
+         * Take a snapshot of the metadata content. We do that in order to protect ourself against
+         * concurrent changes in the metadata object. This protection is needed because we need to
+         * perform multiple passes on the same metadata.
+         */
+        final Map<String,Object> asValueMap = asValueMap(metadata);
+        final Map<String,Object> asSingletons = new LinkedHashMap<>();
+        for (final Map.Entry<String,Object> entry : asValueMap.entrySet()) {
+            asSingletons.put(entry.getKey(), extractFromCollection(entry.getValue()));
+        }
+        /*
+         * Search the database for an existing metadata.
+         */
+        final Class<?> implementationType = metadata.getClass();
+        final Class<?> interfaceType = standard.getInterface(implementationType);
+        final String table = getTableName(interfaceType);
+        final Set<String> columns = getExistingColumns(table);
+        String identifier = search(table, columns, asSingletons, stmt, helper);
+        if (identifier != null) {
+            if (done.put(metadata, identifier) != null) {
+                throw new AssertionError(metadata);
+            }
+            return identifier;
+        }
+        /*
+         * Trim the null values or empty collections. We perform this operation only after the check
+         * for existing entries, in order to take in account null values when checking existing entries.
+         */
+        if (columnCreationPolicy != ValueExistencePolicy.ALL) {
+            for (final Iterator<Object> it = asSingletons.values().iterator(); it.hasNext();) {
+                if (it.next() == null) {
+                    it.remove();
+                }
+            }
+        }
+        /*
+         * Process to the table creation if it does not already exists. If the table has parents, they will be
+         * created first. The later will work only for database supporting table inheritance, like PostgreSQL.
+         * For other kind of database engine, we can not store metadata having parent interfaces.
+         */
+        createTable(stmt, interfaceType, table, columns);
+        /*
+         * Add missing columns if there is any. If columns are added, we will keep trace of foreigner keys in
+         * this process but will not create the constraints now because the foreigner tables may not exist yet.
+         * They will be created later by recursive calls to this method a little bit below.
+         */
+        Map<String,Class<?>> colTypes = null, colTables = null;
+        final Map<String,FKey> foreigners = new LinkedHashMap<>();
+        for (final String column : asSingletons.keySet()) {
+            if (!columns.contains(column)) {
+                if (colTypes == null) {
+                    colTypes  = standard.asTypeMap(implementationType, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.ELEMENT_TYPE);
+                    colTables = standard.asTypeMap(implementationType, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.DECLARING_INTERFACE);
+                }
+                /*
+                 * We have found a column to add. Check if the column actually needs to be added to the parent table
+                 * (if such parent exists). In most case, the answer is "no" and 'addTo' is equals to 'table'.
+                 */
+                String addTo = table;
+                if (helper.dialect.isTableInheritanceSupported) {
+                    @SuppressWarnings("null")
+                    final Class<?> declaring = colTables.get(column);
+                    if (!interfaceType.isAssignableFrom(declaring)) {
+                        addTo = getTableName(declaring);
+                    }
+                }
+                /*
+                 * Determine the column data type.
+                 */
+                int maxLength = maximumValueLength;
+                Class<?> rt = colTypes.get(column);
+                if (CodeList.class.isAssignableFrom(rt) || standard.isMetadata(rt)) {
+                    /*
+                     * Found a reference to an other metadata. Remind that
+                     * column for creating a foreign key constraint later.
+                     */
+                    maxLength = maximumIdentifierLength;
+                    if (foreigners.put(column, new FKey(addTo, rt, null)) != null) {
+                        throw new AssertionError(column);                               // Should never happen.
+                    }
+                    rt = null;                                                          // For forcing VARCHAR type.
+                }
+                stmt.executeUpdate(helper.createColumn(schema(), addTo, column, rt, maxLength));
+                columns.add(column);
+            }
+        }
+        /*
+         * Get the identifier for the new metadata. If no identifier is proposed, we will try to recycle
+         * the identifier of the parent.  For example in ISO 19115, Contact (which contains phone number,
+         * etc.) is associated only to Responsibility. So it make sense to use the Responsibility ID for
+         * the contact info.
+         */
+        identifier = suggestIdentifier(metadata, asValueMap);
+        if (identifier == null) {
+            identifier = parent;
+            if (identifier == null) {
+                /*
+                 * Arbitrarily pickup the first non-metadata attribute.
+                 * Fallback on "unknown" if none are found.
+                 */
+                identifier = "unknown";
+                for (final Object value : asSingletons.values()) {
+                    if (value != null && !standard.isMetadata(value.getClass())) {
+                        identifier = abbreviation(value.toString());
+                        break;
+                    }
+                }
+            }
+        }
+        /*
+         * Check for key collision. We will add a suffix if there is one. Note that the final identifier must be
+         * found before we put its value in the map, otherwise cyclic references (if any) will use the wrong value.
+         *
+         * First, we trim the identifier (primary key) to the maximal length. Then, the loop removes at most four
+         * additional characters if the identifier is still too long. After that point, if the identifier still too
+         * long, we will let the database driver produces its own SQLException.
+         */
+        try (IdentifierGenerator idCheck = new IdentifierGenerator(this, schema(), table, ID_COLUMN, helper)) {
+            for (int i=0; i<MINIMAL_LIMIT-1; i++) {
+                final int maxLength = maximumIdentifierLength - i;
+                if (identifier.length() > maxLength) {
+                    identifier = identifier.substring(0, maxLength);
+                }
+                identifier = idCheck.identifier(identifier);
+                if (identifier.length() <= maximumIdentifierLength) {
+                    break;
+                }
+            }
+        }
+        if (done.put(metadata, identifier) != null) {
+            throw new AssertionError(metadata);
+        }
+        /*
+         * Process all dependencies now. This block may invoke this method recursively.
+         * Once a dependency has been added to the database, the corresponding value in
+         * the 'asMap' HashMap is replaced by the identifier of the dependency we just added.
+         */
+        Map<String,FKey> referencedTables = null;
+        for (final Map.Entry<String,Object> entry : asSingletons.entrySet()) {
+            Object value = entry.getValue();
+            final Class<?> type = value.getClass();
+            if (CodeList.class.isAssignableFrom(type)) {
+                value = addCode(stmt, (CodeList<?>) value);
+            } else if (standard.isMetadata(type)) {
+                String dependency = proxy(value);
+                if (dependency == null) {
+                    dependency = done.get(value);
+                    if (dependency == null) {
+                        dependency = add(stmt, value, done, identifier);
+                        assert done.get(value) == dependency;                       // Really identity comparison.
+                        if (!helper.dialect.isIndexInheritanceSupported) {
+                            /*
+                             * In a classical object-oriented model, the foreigner key constraints declared in the
+                             * parent table would take in account the records in the child table and we would have
+                             * nothing special to do. However PostgreSQL 9.1 does not yet inherit index. So if we
+                             * detect that a column references some records in two different tables, then we must
+                             * suppress the foreigner key constraint.
+                             */
+                            final String column = entry.getKey();
+                            final Class<?> targetType = standard.getInterface(value.getClass());
+                            FKey fkey = foreigners.get(column);
+                            if (fkey != null && !targetType.isAssignableFrom(fkey.tableType)) {
+                                /*
+                                 * The foreigner key constraint does not yet exist, so we can
+                                 * change the target table. Set the target to the child table.
+                                 */
+                                fkey.tableType = targetType;
+                            }
+                            if (fkey == null) {
+                                /*
+                                 * The foreigner key constraint may already exist. Get a list of all foreigner keys for
+                                 * the current table, then verify if the existing constraint references the right table.
+                                 */
+                                if (referencedTables == null) {
+                                    referencedTables = new HashMap<>();
+                                    try (ResultSet rs = stmt.getConnection().getMetaData().getImportedKeys(CATALOG, schema(), table)) {
+                                        while (rs.next()) {
+                                            if ((schema() == null || schema().equals(rs.getString("PKTABLE_SCHEM"))) &&
+                                                (CATALOG  == null || CATALOG.equals(rs.getString("PKTABLE_CAT"))))
+                                            {
+                                                referencedTables.put(rs.getString("FKCOLUMN_NAME"),
+                                                            new FKey(rs.getString("PKTABLE_NAME"), null,
+                                                                     rs.getString("FK_NAME")));
+                                            }
+                                        }
+                                    }
+                                }
+                                fkey = referencedTables.remove(column);
+                                if (fkey != null && !fkey.tableName.equals(getTableName(targetType))) {
+                                    /*
+                                     * The existing foreigner key constraint doesn't reference the right table.
+                                     * We have no other choice than removing it...
+                                     */
+                                    stmt.executeUpdate(helper.clear().append("ALTER TABLE ")
+                                            .appendIdentifier(schema(), table).append(" DROP CONSTRAINT ")
+                                            .appendIdentifier(fkey.keyName).toString());
+                                    warning(MetadataWriter.class, "add", Messages.getResources(null)
+                                            .getLogRecord(Level.WARNING, Messages.Keys.DroppedForeignerKey_1,
+                                            table + '.' + column + " ⇒ " + fkey.tableName + '.' + ID_COLUMN));
+                                }
+                            }
+                        }
+                    }
+                }
+                value = dependency;
+            }
+            entry.setValue(value);
+        }
+        /*
+         * Now that all dependencies have been inserted in the database, we can setup the foreigner key constraints
+         * if there is any. Note that we deferred the foreigner key creations not because of the missing rows,
+         * but because of missing tables (since new tables may be created in the process of inserting dependencies).
+         */
+        if (!foreigners.isEmpty()) {
+            for (final Map.Entry<String,FKey> entry : foreigners.entrySet()) {
+                final FKey fkey = entry.getValue();
+                Class<?> rt = fkey.tableType;
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
+                final String primaryKey;
+                if (isCodeList) {
+                    primaryKey = CODE_COLUMN;
+                } else {
+                    primaryKey = ID_COLUMN;
+                    rt = standard.getInterface(rt);
+                }
+                final String column = entry.getKey();
+                final String target = getTableName(rt);
+                stmt.executeUpdate(helper.createForeignKey(
+                        schema(), fkey.tableName, column,               // Source (schema.table.column)
+                        target, primaryKey,                             // Target (table.column)
+                        !isCodeList));                                  // CASCADE if metadata, RESTRICT if CodeList.
+                /*
+                 * In a classical object-oriented model, the constraint would be inherited by child tables.
+                 * However this is not yet supported as of PostgreSQL 9.6. If inheritance is not supported,
+                 * then we have to repeat the constraint creation in child tables.
+                 */
+                if (!helper.dialect.isIndexInheritanceSupported && !table.equals(fkey.tableName)) {
+                    stmt.executeUpdate(helper.createForeignKey(schema(), table, column, target, primaryKey, !isCodeList));
+                }
+            }
+        }
+        /*
+         * Create the SQL statement which will insert the data.
+         */
+        helper.clear().append("INSERT INTO ").appendIdentifier(schema(), table).append(" (").append(ID_COLUMN);
+        for (final String column : asSingletons.keySet()) {
+            helper.append(", ").appendIdentifier(column);
+        }
+        helper.append(") VALUES (").appendValue(identifier);
+        for (final Object value : asSingletons.values()) {
+            helper.append(", ").appendValue(value);
+        }
+        final String sql = helper.append(')').toString();
+        if (stmt.executeUpdate(sql) != 1) {
+            throw new SQLException(Errors.format(Errors.Keys.DatabaseUpdateFailure_3, 0, table, identifier));
+        }
+        return identifier;
+    }
+
+    /**
+     * Information about the source and the target of a foreigner key. This class stores only the table names
+     * (indirectly in the case of {@link #tableType}, since the name is derived from the type).
+     * The column name are known by other way: either as the map key in the case of the source,
+     * or fixed to {@value MetadataWriter#ID_COLUMN} in the case of the target.
+     */
+    private static final class FKey {
+        final String tableName;                 // May be source or target, depending on the context.
+        Class<?>     tableType;                 // Always the target table.
+        final String keyName;
+
+        FKey(final String tableName, final Class<?> tableType, final String keyName) {
+            this.tableName = tableName;
+            this.tableType = tableType;
+            this.keyName   = keyName;
+        }
+    }
+
+    /**
+     * Creates a table for the given type, if the table does not already exists.
+     * This method may call itself recursively for creating parent tables, if they do not exist neither.
+     *
+     * @param  stmt     the statement to use for creating tables.
+     * @param  type     the interface class.
+     * @param  table    the name of the table (should be consistent with the type).
+     * @param  columns  the existing columns, as an empty set if the table does not exist yet.
+     * @throws SQLException if an error occurred while creating the table.
+     */
+    private void createTable(final Statement stmt, final Class<?> type, final String table, final Set<String> columns)
+            throws SQLException
+    {
+        if (columns.isEmpty()) {
+            StringBuilder inherits = null;
+            for (final Class<?> candidate : type.getInterfaces()) {
+                if (standard.isMetadata(candidate)) {
+                    final SQLBuilder helper = helper();
+                    if (helper.dialect.isTableInheritanceSupported) {
+                        final String parent = getTableName(candidate);
+                        createTable(stmt, candidate, parent, getExistingColumns(parent));
+                        if (inherits == null) {
+                            helper.clear().append("CREATE TABLE ").appendIdentifier(schema(), table);
+                            if (!helper.dialect.isIndexInheritanceSupported) {
+                                /*
+                                 * In a classical object-oriented model, the new child table would inherit the index from
+                                 * its parent table. However this is not yet the case as of PostgreSQL 9.6. If the index is
+                                 * 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(")) ");
+                            }
+                            inherits = new StringBuilder(helper.append(" INHERITS (").toString());
+                        } else {
+                            inherits.append(", ");
+                        }
+                        inherits.append(helper.clear().appendIdentifier(schema(), parent));
+                    }
+                }
+            }
+            final String sql;
+            if (inherits != null) {
+                sql = inherits.append(')').toString();
+            } else {
+                sql = createTable(table, ID_COLUMN);
+            }
+            stmt.executeUpdate(sql);
+            columns.add(ID_COLUMN);
+        }
+    }
+
+    /**
+     * Returns the SQL statement for creating the given table with the given primary key.
+     * This method returns a string of the following form:
+     *
+     * {@preformat sql
+     *     CREATE TABLE "schema"."table" (primaryKey VARCHAR(20) NOT NULL PRIMARY KEY)
+     * }
+     */
+    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(") NOT NULL PRIMARY KEY)").toString();
+    }
+
+    /**
+     * Adds a code list if it is not already present. This is used only in order to enforce
+     * foreigner key constraints in the database. The value of CodeList tables are not used
+     * at parsing time.
+     */
+    private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException {
+        assert Thread.holdsLock(this);
+        final String table = getTableName(code.getClass());
+        final Set<String> columns = getExistingColumns(table);
+        if (columns.isEmpty()) {
+            stmt.executeUpdate(createTable(table, CODE_COLUMN));
+            columns.add(CODE_COLUMN);
+        }
+        final String identifier = code.name();
+        final String query = helper().clear().append("SELECT ").append(CODE_COLUMN)
+                .append(" FROM ").appendIdentifier(schema(), table).append(" WHERE ")
+                .append(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(')').toString();
+            if (stmt.executeUpdate(sql) != 1) {
+                throw new SQLException(Errors.format(Errors.Keys.DatabaseUpdateFailure_3, 0, table, identifier));
+            }
+        }
+        return identifier;
+    }
+
+    /**
+     * 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.
+     *
+     * <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>
+     *
+     * @param  metadata    the metadata instance for which to suggests an identifier.
+     * @param  asValueMap  a view of all metadata properties as a map.
+     *                     Keys are {@linkplain KeyNamePolicy#UML_IDENTIFIER UML identifiers}.
+     * @return the proposed identifier, or {@code null} if this method does not have any suggestion.
+     * @throws SQLException if an access to the database was desired but failed.
+     */
+    protected String suggestIdentifier(final Object metadata, final Map<String,Object> asValueMap) throws SQLException {
+        String identifier = null;
+        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;
+            }
+        }
+        if (identifier == null && metadata instanceof Citation) {
+            identifier = nonEmpty(Citations.getIdentifier((Citation) metadata));
+        }
+        if (identifier == null) {
+            final TitleProperty tp = metadata.getClass().getAnnotation(TitleProperty.class);
+            if (tp != null) {
+                final Object value = asValueMap.get(nonEmpty(tp.name()));
+                if (value != null) {
+                    identifier = nonEmpty(value.toString());
+                }
+            }
+        }
+        /*
+         * At this point we got a suggested identifier, but it may be quite long.
+         * For example it may be a citation title. Try to make an abbreviation.
+         */
+        if (identifier != null && identifier.length() >= 8) {                   // Arbitrary threshold.
+            identifier = abbreviation(identifier);
+        }
+        return identifier;
+    }
+
+    /**
+     * Returns an abbreviation of the given identifier, if one is found.
+     */
+    private static String abbreviation(final String identifier) {
+        final StringBuilder buffer = new StringBuilder();
+        final StringTokenizer tokens = new StringTokenizer(identifier);
+        while (tokens.hasMoreTokens()) {
+            buffer.appendCodePoint(tokens.nextToken().codePointAt(0));
+        }
+        if (buffer.length() >= 3) {
+            return buffer.toString();
+        }
+        return identifier;
+    }
+
+    /**
+     * Trims leading and trailing spaces and returns the given value if non-empty, or {@code null} otherwise.
+     */
+    private static String nonEmpty(String value) {
+        if (value != null) {
+            value = value.trim();
+            if (value.isEmpty()) {
+                value = null;
+            }
+        }
+        return value;
+    }
+}

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

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

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java [UTF-8] Mon May  8 00:11:14 2017
@@ -42,7 +42,7 @@ public final strictfp class ScriptRunner
      */
     @Test
     public void testOnDerby() throws Exception {
-        final DataSource ds = TestDatabase.create("temporary");
+        final DataSource ds = TestDatabase.create("ScriptRunner");
         try (Connection c = ds.getConnection()) {
             final ScriptRunner sr = new ScriptRunner(c, 3);
             testSupportedFlags(sr);

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/IdentifierGeneratorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/IdentifierGeneratorTest.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/IdentifierGeneratorTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/IdentifierGeneratorTest.java [UTF-8] Mon May  8 00:11:14 2017
@@ -59,9 +59,9 @@ public final strictfp class IdentifierGe
      */
     @Test
     public void testSequence() throws Exception {
-        final DataSource ds = TestDatabase.create("identifiers");
+        final DataSource ds = TestDatabase.create("IdentifierGenerator");
         try {
-            final MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, ds, null, null, 2);
+            final MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, ds, null, null);
             synchronized (source) {
                 stmt = source.connection().createStatement();
                 stmt.executeUpdate("CREATE TABLE \"" + TABLE + "\" (ID VARCHAR(6) NOT NULL PRIMARY KEY)");

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java [UTF-8] Mon May  8 00:11:14 2017
@@ -52,8 +52,8 @@ public final strictfp class MetadataSour
      */
     @Test
     public void testOnDerby() throws Exception {
-        final DataSource ds = TestDatabase.create("temporary");
-        try (MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, ds, "metadata", null, null)) {
+        final DataSource ds = TestDatabase.create("MetadataSource");
+        try (MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, ds, "metadata", null)) {
             source.install();
             verifyFormats(source);
             testSearch(source);

Added: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java?rev=1794274&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java [UTF-8] Mon May  8 00:11:14 2017
@@ -0,0 +1,182 @@
+/*
+ * 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 javax.sql.DataSource;
+import org.postgresql.ds.PGSimpleDataSource;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.PresentationForm;
+import org.opengis.metadata.citation.OnLineFunction;
+import org.opengis.metadata.citation.OnlineResource;
+import org.opengis.metadata.citation.Party;
+import org.opengis.metadata.citation.Role;
+import org.apache.sis.internal.metadata.sql.TestDatabase;
+import org.apache.sis.metadata.iso.citation.HardCodedCitations;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.test.TestUtilities;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOn;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Creates a metadata database, stores a few elements and read them back.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+@DependsOn({
+    MetadataSourceTest.class,
+    IdentifierGeneratorTest.class
+})
+public final strictfp class MetadataWriterTest extends TestCase {
+    /**
+     * The data source providing connections to the database.
+     */
+    private MetadataWriter source;
+
+    /**
+     * Runs all tests on JavaDB in the required order.
+     *
+     * @throws Exception if an error occurred while writing or reading the database.
+     */
+    @Test
+    public void testDerby() throws Exception {
+        final DataSource ds = TestDatabase.create("MetadataWriter");
+        source = new MetadataWriter(MetadataStandard.ISO_19115, ds, null, null);
+        try {
+            write();
+            search();
+//          read();
+            source.close();
+        } finally {
+            TestDatabase.drop(ds);
+        }
+    }
+
+    /**
+     * Runs all tests on PostgreSQL in the required order. This test is disabled by default
+     * because it requires manual setup of a test database.
+     *
+     * @throws Exception if an error occurred while writing or reading the database.
+     */
+    @Test
+    @Ignore("This test need to be run manually on a machine having a local PostgreSQL database.")
+    public void testPostgreSQL() throws Exception {
+        final PGSimpleDataSource ds = new PGSimpleDataSource();
+        ds.setServerName("localhost");
+        ds.setDatabaseName("SpatialMetadataTest");
+        source = new MetadataWriter(MetadataStandard.ISO_19115, ds, "metadata", null);
+        try {
+            write();
+            search();
+            read();
+        } finally {
+            source.close();
+        }
+    }
+
+    /**
+     * Creates a new temporary database and write elements in it.
+     *
+     * @throws MetadataStoreException if an error occurred while writing or reading the database.
+     */
+    private void write() throws MetadataStoreException {
+        assertEquals("ISO 19115", source.add(HardCodedCitations.ISO_19115));
+        assertEquals("EPSG",      source.add(HardCodedCitations.EPSG));
+        assertEquals("SIS",       source.add(HardCodedCitations.SIS));
+    }
+
+    /**
+     * Searches known entries in the database.
+     *
+     * @throws MetadataStoreException if an error occurred while reading the database.
+     */
+    private void search() throws MetadataStoreException {
+        assertNull  ("ISO 19111", source.search(HardCodedCitations.ISO_19111));
+        assertEquals("ISO 19115", source.search(HardCodedCitations.ISO_19115));
+        assertEquals("EPSG",      source.search(HardCodedCitations.EPSG));
+        assertEquals("SIS",       source.search(HardCodedCitations.SIS));
+        assertNull  ("ISO 19111", source.search(HardCodedCitations.ISO_19111));
+        assertEquals("EPSG",      source.search(TestUtilities.getSingleton(
+                HardCodedCitations.EPSG.getCitedResponsibleParties())));
+    }
+
+    /**
+     * Reads known entries in the database.
+     * Expected entry is:
+     *
+     * {@preformat text
+     *   Citation
+     *     ├─Title………………………………………………………… EPSG Geodetic Parameter Dataset
+     *     ├─Identifier
+     *     │   └─Code………………………………………………… EPSG
+     *     ├─Cited responsible party
+     *     │   ├─Party
+     *     │   │   ├─Name……………………………………… International Association of Oil & Gas Producers
+     *     │   │   └─Contact info
+     *     │   │       └─Online resource
+     *     │   │           ├─Linkage………… http://www.epsg.org
+     *     │   │           └─Function……… Information
+     *     │   └─Role………………………………………………… Principal investigator
+     *     └─Presentation form………………………… Table digital
+     * }
+     *
+     * @throws MetadataStoreException if an error occurred while reading the database.
+     */
+    private void read() throws MetadataStoreException {
+        final Citation c = source.lookup(Citation.class, "EPSG");
+        assertEquals("EPSG Geodetic Parameter Dataset", c.getTitle().toString());
+        assertEquals(PresentationForm.TABLE_DIGITAL, TestUtilities.getSingleton(c.getPresentationForms()));
+        /*
+         * Ask for dependencies that are known to exist.
+         */
+        final Responsibility responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
+        assertEquals(Role.PRINCIPAL_INVESTIGATOR, responsible.getRole());
+
+        final Party party = TestUtilities.getSingleton(responsible.getParties());
+        assertEquals("International Association of Oil & Gas Producers", party.getName().toString());
+
+        OnlineResource resource = TestUtilities.getSingleton(TestUtilities.getSingleton(party.getContactInfo()).getOnlineResources());
+        assertEquals("http://www.epsg.org", resource.getLinkage().toString());
+        assertEquals(OnLineFunction.INFORMATION, resource.getFunction());
+        /*
+         * Ask columns that are known to not exist.
+         */
+        assertNull(c.getEditionDate());
+        assertTrue(c.getDates().isEmpty());
+        assertEquals(0, c.getAlternateTitles().size());
+        /*
+         * Test the cache.
+         */
+        assertSame   (c, source.lookup(Citation.class, "EPSG"));
+        assertNotSame(c, source.lookup(Citation.class, "SIS"));
+        /*
+         * Should return the identifier with no search. Actually the real test is the call to "proxy",
+         * since there is no way to ensure that the call to "search" tooks the short path (except by
+         * looking at the debugger). But if "proxy" succeed, then "search" should be okay.
+         */
+        assertEquals("EPSG", source.proxy (c));
+        assertEquals("EPSG", source.search(c));
+    }
+}

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

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

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] Mon May  8 00:11:14 2017
@@ -107,7 +107,8 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.metadata.sql.TypeMapperTest.class,
     org.apache.sis.internal.metadata.sql.ScriptRunnerTest.class,
     org.apache.sis.metadata.sql.IdentifierGeneratorTest.class,
-    org.apache.sis.metadata.sql.MetadataSourceTest.class
+    org.apache.sis.metadata.sql.MetadataSourceTest.class,
+    org.apache.sis.metadata.sql.MetadataWriterTest.class
 })
 public final strictfp class MetadataTestSuite extends TestSuite {
     /**

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java [UTF-8] Mon May  8 00:11:14 2017
@@ -27,6 +27,7 @@ import java.sql.Connection;
 import java.sql.Statement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import org.postgresql.ds.PGSimpleDataSource;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
@@ -129,13 +130,13 @@ public final strictfp class EPSGInstalle
     @Test
     public void testCreationOnDerby() throws Exception {
         final InstallationScriptProvider scripts = getScripts();            // Needs to be invoked first.
-        final DataSource ds = TestDatabase.create("test");
+        final DataSource ds = TestDatabase.create("EPSGInstaller");
         try {
             createAndTest(ds, scripts);
         } finally {
             TestDatabase.drop(ds);
         }
-        loggings.assertNextLogContains("EPSG", "jdbc:derby:memory:test");
+        loggings.assertNextLogContains("EPSG", "jdbc:derby:memory:EPSGInstaller");
         loggings.assertNoUnexpectedLog();
     }
 
@@ -149,7 +150,7 @@ public final strictfp class EPSGInstalle
     public void testCreationOnHSQLDB() throws Exception {
         final InstallationScriptProvider scripts = getScripts();            // Needs to be invoked first.
         final DataSource ds = (DataSource) Class.forName("org.hsqldb.jdbc.JDBCDataSource").newInstance();
-        ds.getClass().getMethod("setURL", String.class).invoke(ds, "jdbc:hsqldb:mem:test");
+        ds.getClass().getMethod("setURL", String.class).invoke(ds, "jdbc:hsqldb:mem:EPSGInstaller");
         try {
             createAndTest(ds, scripts);
         } finally {
@@ -157,7 +158,7 @@ public final strictfp class EPSGInstalle
                 s.execute("SHUTDOWN");
             }
         }
-        loggings.assertNextLogContains("EPSG", "jdbc:hsqldb:mem:test");
+        loggings.assertNextLogContains("EPSG", "jdbc:hsqldb:mem:EPSGInstaller");
         loggings.assertNoUnexpectedLog();
     }
 
@@ -176,10 +177,9 @@ public final strictfp class EPSGInstalle
     @Ignore("This test need to be run manually on a machine having a local PostgreSQL database.")
     public void testCreationOnPostgreSQL() throws Exception {
         final InstallationScriptProvider scripts = getScripts();            // Needs to be invoked first.
-        final DataSource ds = (DataSource) Class.forName("org.postgresql.ds.PGSimpleDataSource").newInstance();
-        final Class<?> dsc = ds.getClass();
-        dsc.getMethod("setServerName",   String.class).invoke(ds, "localhost");
-        dsc.getMethod("setDatabaseName", String.class).invoke(ds, "SpatialMetadataTest");
+        final PGSimpleDataSource ds = new PGSimpleDataSource();
+        ds.setServerName("localhost");
+        ds.setDatabaseName("SpatialMetadataTest");
         createAndTest(ds, scripts);
         loggings.assertNextLogContains("EPSG", "jdbc:postgresql://localhost/SpatialMetadataTest");
         loggings.assertNoUnexpectedLog();

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Mon May  8 00:11:14 2017
@@ -186,6 +186,11 @@ public final class Errors extends Indexe
         public static final short DatabaseError_2 = 21;
 
         /**
+         * Failed to {0,choice,0#insert|1#update} record “{2}” in database table “{1}”.
+         */
+        public static final short DatabaseUpdateFailure_3 = 174;
+
+        /**
          * Thread “{0}” is dead.
          */
         public static final short DeadThread_1 = 22;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Mon May  8 00:11:14 2017
@@ -48,6 +48,7 @@ ClassNotFinal_1                   = Clas
 CloneNotSupported_1               = Can not clone an object of type \u2018{0}\u2019.
 CrossReferencesNotSupported       = Cross references are not supported.
 DatabaseError_2                   = Database error while creating a \u2018{0}\u2019 object for the \u201c{1}\u201d identifier.
+DatabaseUpdateFailure_3           = Failed to {0,choice,0#insert|1#update} record \u201c{2}\u201d in database table \u201c{1}\u201d.
 DeadThread_1                      = Thread \u201c{0}\u201d is dead.
 DisposedInstanceOf_1              = This instance of \u2018{0}\u2019 has been disposed.
 DuplicatedElement_1               = Element \u201c{0}\u201d is duplicated.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Mon May  8 00:11:14 2017
@@ -45,6 +45,7 @@ ClassNotFinal_1                   = La c
 CloneNotSupported_1               = Un objet de type \u2018{0}\u2019 ne peut pas \u00eatre clon\u00e9.
 CrossReferencesNotSupported       = Les r\u00e9f\u00e9rences crois\u00e9es ne sont pas support\u00e9es.
 DatabaseError_2                   = Erreur de base de donn\u00e9es lors de la cr\u00e9ation d\u2019un objet \u2018{0}\u2019 pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb.
+DatabaseUpdateFailure_3           = \u00c9chec lors de {0,choice,0#l\u2019insertion|1#la mise \u00e0 jour} de l\u2019enregistrement \u00ab\u202f{2}\u202f\u00bb dans la table \u00ab\u202f{1}\u202f\u00bb de la base de donn\u00e9es.
 DeadThread_1                      = La t\u00e2che \u00ab\u202f{0}\u202f\u00bb est morte.
 DisposedInstanceOf_1              = Cette instance de \u2018{0}\u2019 a \u00e9t\u00e9 dispos\u00e9e.
 DuplicatedElement_1               = L\u2019\u00e9lement \u00ab\u202f{0}\u202f\u00bb est dupliqu\u00e9.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] Mon May  8 00:11:14 2017
@@ -154,6 +154,11 @@ public final class Messages extends Inde
         public static final short DiscardedExclusiveProperty_2 = 19;
 
         /**
+         * Dropped the “{0}” foreigner key constraint.
+         */
+        public static final short DroppedForeignerKey_1 = 32;
+
+        /**
          * Ignored properties after the first occurrence of ‘{0}’.
          */
         public static final short IgnoredPropertiesAfterFirst_1 = 20;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] Mon May  8 00:11:14 2017
@@ -34,6 +34,7 @@ DataDirectoryNotReadable_2       = The \
 DataDirectoryNotSpecified_1      = The \u201c{0}\u201d environment variable is not set.
 DataDirectoryNotWritable_2       = Apache SIS can not write in the \u201c{1}\u201d directory given by the {0} environment variable.
 DiscardedExclusiveProperty_2     = Property \u201c{0}\u201d has been discarded in favor of \u201c{1}\u201d, because those two properties are mutually exclusive.
+DroppedForeignerKey_1            = Dropped the \u201c{0}\u201d foreigner key constraint.
 IgnoredPropertiesAfterFirst_1    = Ignored properties after the first occurrence of \u2018{0}\u2019.
 IgnoredPropertyAssociatedTo_1    = Ignored property associated to \u2018{0}\u2019.
 IncompleteParsing_1              = Parsing of \u201c{0}\u201d done, but some elements were ignored.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] Mon May  8 00:11:14 2017
@@ -41,6 +41,7 @@ DataDirectoryNotReadable_2       = Le r\
 DataDirectoryNotSpecified_1      = La variable environnementale \u00ab\u202f{0}\u202f\u00bb n\u2019est pas d\u00e9finie.
 DataDirectoryNotWritable_2       = Apache SIS ne peut pas \u00e9crire dans le r\u00e9pertoire \u00ab\u202f{1}\u202f\u00bb sp\u00e9cifi\u00e9e par la variable environnementale {0}.
 DiscardedExclusiveProperty_2     = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb a \u00e9t\u00e9 \u00e9cart\u00e9e en faveur de \u00ab\u202f{1}\u202f\u00bb, parce que ces deux propri\u00e9t\u00e9s sont mutuellement exclusives.
+DroppedForeignerKey_1            = Suppression de la contrainte de cl\u00e9 \u00e9trang\u00e8re \u00ab\u202f{0}\u202f\u00bb.
 IgnoredPropertiesAfterFirst_1    = Des propri\u00e9t\u00e9s ont \u00e9t\u00e9 ignor\u00e9es apr\u00e8s la premi\u00e8re occurrence de \u2018{0}\u2019.
 IgnoredPropertyAssociatedTo_1    = Une propri\u00e9t\u00e9 associ\u00e9e \u00e0 \u2018{0}\u2019 a \u00e9t\u00e9 ignor\u00e9e.
 IncompleteParsing_1              = La lecture de \u00ab\u202f{0}\u202f\u00bb a \u00e9t\u00e9 faite, mais en ignorant certains \u00e9l\u00e9ments.

Modified: sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] Mon May  8 00:11:14 2017
@@ -106,7 +106,7 @@ junit.version        = 4.12
 hamcrest.version     = 1.3
 jaxb-ns-mapper       = 2.2.4
 hsqldb.version       = 2.3.4
-postgresql.version   = 42.0.0
+postgresql.version   = 42.1.1
 
 #
 # Classpaths for compilation, execution and tests.
@@ -131,6 +131,7 @@ javac.test.classpath=\
     ${javac.classpath}:\
     ${maven.repository}/junit/junit/${junit.version}/junit-${junit.version}.jar:\
     ${maven.repository}/org/hamcrest/hamcrest-core/${hamcrest.version}/hamcrest-core-${hamcrest.version}.jar:\
+    ${maven.repository}/org/postgresql/postgresql/${postgresql.version}/postgresql-${postgresql.version}.jar:\
     ${maven.repository}/gov/nist/math/jama/${jama.version}/jama-${jama.version}.jar:\
     ${project.GeoAPI}/dist/geoapi-tests.jar:\
     ${build.classes.dir}
@@ -143,7 +144,6 @@ run.test.classpath=\
     ${javac.test.classpath}:\
     ${build.test.classes.dir}:\
     ${maven.repository}/org/hsqldb/hsqldb/${hsqldb.version}/hsqldb-${hsqldb.version}.jar:\
-    ${maven.repository}/org/postgresql/postgresql/${postgresql.version}/postgresql-${postgresql.version}.jar:\
     ${maven.repository}/org/jdom/jdom2/${jdom2.version}/jdom2-${jdom2.version}.jar:\
     ${maven.repository}/edu/ucar/udunits/${netcdf.version}/udunits-${netcdf.version}.jar:\
     ${maven.repository}/edu/ucar/httpservices/${netcdf.version}/httpservices-${netcdf.version}.jar:\

Modified: sis/branches/JDK8/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/pom.xml?rev=1794274&r1=1794273&r2=1794274&view=diff
==============================================================================
--- sis/branches/JDK8/pom.xml (original)
+++ sis/branches/JDK8/pom.xml Mon May  8 00:11:14 2017
@@ -438,7 +438,7 @@ Apache SIS is a free software, Java lang
       <dependency>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
-        <version>42.0.0</version>
+        <version>42.1.1</version>
         <scope>test</scope>
       </dependency>