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 d4de6823223b34a1220e03b387b760fec4940f33
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Oct 4 12:43:38 2024 +0200

    Reduce the amount of logs emitted when the EPSG database is not available.
    CommonCRS synchronization has been made less fine-grained (which also 
simplifies the code)
    because the previous strategy did not prevent duplicated log record under 
race conditions.
    The results were correct, but the duplicated log records were confusing.
---
 .../sis/metadata/sql/privy/LocalDataSource.java    |   6 +-
 .../main/org/apache/sis/xml/XML.java               |   2 +-
 .../main/org/apache/sis/xml/bind/Context.java      |   1 +
 .../main/org/apache/sis/io/wkt/Warnings.java       |   2 +-
 .../apache/sis/referencing/AuthorityFactories.java |  23 +-
 .../main/org/apache/sis/referencing/CommonCRS.java | 324 +++++++++------------
 .../factory/ConcurrentAuthorityFactory.java        |   2 +-
 .../factory/IdentifiedObjectFinder.java            |  14 +-
 .../sis/referencing/factory/sql/EPSGFactory.java   |  15 +-
 .../sis/referencing/factory/sql/EPSGInstaller.java |   5 +-
 .../referencing/internal/ServicesForMetadata.java  |   3 +-
 .../operation/builder/ProjectedTransformTry.java   |   3 +-
 .../apache/sis/geometry/DirectPosition1DTest.java  |   4 +-
 .../main/org/apache/sis/io/stream/IOUtilities.java |   5 +-
 .../apache/sis/storage/event/StoreListeners.java   |  10 +-
 .../main/org/apache/sis/system/DataDirectory.java  |   3 +-
 .../main/org/apache/sis/util/Exceptions.java       |  24 +-
 .../main/org/apache/sis/util/logging/Logging.java  |   6 +-
 .../sis/util/resources/IndexedResourceBundle.java  |  18 +-
 .../test/org/apache/sis/test/Assertions.java       |   5 +-
 .../sis/storage/gsf/panama/LibraryLoader.java      |   4 +-
 .../apache/sis/gui/referencing/AuthorityCodes.java |   3 +-
 .../apache/sis/storage/panama/LibraryLoader.java   |   4 +-
 .../org/apache/sis/storage/panama/Resources.java   |   2 +-
 .../apache/sis/storage/panama/Resources.properties |   2 +-
 .../sis/storage/panama/Resources_fr.properties     |   2 +-
 26 files changed, 221 insertions(+), 271 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/LocalDataSource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/LocalDataSource.java
index ee01414623..5e18c53a63 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/LocalDataSource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/LocalDataSource.java
@@ -290,7 +290,7 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
                 }
             }
         } catch (SQLException e) {                              // This is the 
expected exception.
-            final LogRecord record = new LogRecord(Level.FINE, e.getMessage());
+            final var record = new LogRecord(Level.FINER, e.getMessage());
             if (dialect != Dialect.DERBY || !isSuccessfulShutdown(e)) {
                 record.setLevel(Level.WARNING);
                 record.setThrown(e);
@@ -311,8 +311,8 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
      */
     public static boolean isSuccessfulShutdown(final SQLException e) {
         final String state = e.getSQLState();
-        return "08006".equals(state) ||     // Database "SpatialMetadata" 
shutdown.
-               "XJ004".equals(state);       // Database "SpatialMetadata" not 
found (may happen if we failed to open it in the first place).
+        return "08006".equals(state) ||     // Database shutdown.
+               "XJ004".equals(state);       // Database not found (may happen 
if we failed to open it in the first place).
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
index 01f5a5f070..80958b3447 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
@@ -367,7 +367,7 @@ public final class XML extends Static {
      * Specifies a listener to be notified when a non-fatal error occurred 
during the (un)marshalling.
      * The value for this property shall be an instance of {@link Filter}.
      *
-     * <p>By default, warnings that occur during the (un)marshalling process 
are logged. However if a
+     * <p>By default, warnings that occur during the (un)marshalling process 
are logged. However, if a
      * property is set for this key, then the {@link 
Filter#isLoggable(LogRecord)} method will be invoked.
      * If that method returns {@code false}, then the warning will not be 
logged by the (un)marshaller.</p>
      *
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
index 30891b3244..006ebf9d9f 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
@@ -837,6 +837,7 @@ public final class Context extends MarshalContext {
                 if (!logFilter.isLoggable(record)) {
                     return;
                 }
+                record.setThrown(null);
             }
         }
         /*
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Warnings.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Warnings.java
index 846c5f7bfe..095fec6c37 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Warnings.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Warnings.java
@@ -363,7 +363,7 @@ public final class Warnings implements Localized, 
Serializable {
                 if (cause != null) {
                     String details = Exceptions.getLocalizedMessage(cause, 
locale);
                     if (details == null) {
-                        details = cause.toString();
+                        details = Classes.getShortClassName(cause);
                     }
                     buffer.append(lineSeparator).append("   ").append(details);
                 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
index 8b5d66f2dd..7ecce9566b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
@@ -206,19 +206,20 @@ final class AuthorityFactories<T extends 
AuthorityFactory> extends LazySet<T> {
      */
     static boolean isUnavailable(final UnavailableFactoryException e) {
         final AuthorityFactory unavailable = e.getUnavailableFactory();
-        if (!(unavailable instanceof EPSGFactoryFallback)) {
-            if (e.getCause() instanceof SQLTransientException) {
-                return false;
-            }
-            synchronized (AuthorityFactories.class) {
-                if (unavailable == EPSG) {
-                    ALL.reload();   // Must be before setting the `EPSG` field.
-                    EPSG = EPSGFactoryFallback.INSTANCE;
-                    return false;
-                }
+        if (unavailable instanceof EPSGFactoryFallback) {
+            return true;
+        }
+        if (e.getCause() instanceof SQLTransientException) {
+            // Not definitive, but caller should still throw an exception or 
log a warning.
+            return true;
+        }
+        synchronized (AuthorityFactories.class) {
+            if (unavailable == EPSG) {
+                ALL.reload();   // Must be before setting the `EPSG` field.
+                EPSG = EPSGFactoryFallback.INSTANCE;
             }
         }
-        return true;
+        return false;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index 68d218eee4..33edfaf6e3 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -359,35 +359,35 @@ public enum CommonCRS {
      * on which method has been invoked. The kind of object stored in this 
field may change during the
      * application execution.
      */
-    private transient volatile IdentifiedObject cached;
+    private transient IdentifiedObject cached;
 
     /**
      * The normalized geographic CRS, created when first needed.
      *
      * @see #normalizedGeographic()
      */
-    private transient volatile GeographicCRS cachedNormalized;
+    private transient GeographicCRS cachedNormalized;
 
     /**
      * The three-dimensional geographic CRS, created when first needed.
      *
      * @see #geographic3D()
      */
-    private transient volatile GeographicCRS cachedGeo3D;
+    private transient GeographicCRS cachedGeo3D;
 
     /**
      * The geocentric CRS using Cartesian coordinate system, created when 
first needed.
      *
      * @see #geocentric()
      */
-    private transient volatile GeodeticCRS cachedGeocentric;
+    private transient GeodeticCRS cachedGeocentric;
 
     /**
      * The geocentric CRS using spherical coordinate system, created when 
first needed.
      *
      * @see #spherical()
      */
-    private transient volatile GeodeticCRS cachedSpherical;
+    private transient GeodeticCRS cachedSpherical;
 
     /**
      * The Universal Transverse Mercator (UTM) or Universal Polar 
Stereographic (UPS) projections,
@@ -455,6 +455,7 @@ public enum CommonCRS {
         cachedGeo3D      = null;
         cachedNormalized = null;
         cachedGeocentric = null;
+        cachedSpherical  = null;
         synchronized (cachedProjections) {
             cachedProjections.clear();
         }
@@ -601,19 +602,19 @@ public enum CommonCRS {
      * @see DefaultGeographicCRS#forConvention(AxesConvention)
      * @see AxesConvention#NORMALIZED
      */
-    public GeographicCRS normalizedGeographic() {
-        GeographicCRS object = cachedNormalized;
-        if (object == null) {
+    public synchronized GeographicCRS normalizedGeographic() {
+        /*
+         * Note on synchronization: a previous version of this class was using 
volatile fields for the caches,
+         * and kept the synchronized blocks as small as possible. It has been 
replaced by simpler synchronized
+         * methods in order to avoid race conditions which resulted in 
duplicated and confusing log messages
+         * when the EPSG factory is not available.
+         */
+        if (cachedNormalized == null) {
             DefaultGeographicCRS crs = 
DefaultGeographicCRS.castOrCopy(geographic());
             crs = crs.forConvention(AxesConvention.RIGHT_HANDED);       // 
Equivalent to NORMALIZED in our cases, but faster.
-            synchronized (this) {
-                object = cachedNormalized;
-                if (object == null) {
-                    cachedNormalized = object = crs;
-                }
-            }
+            cachedNormalized = crs;
         }
-        return object;
+        return cachedNormalized;
     }
 
     /**
@@ -641,12 +642,11 @@ public enum CommonCRS {
      * @see CRS#forCode(String)
      * @see DefaultGeographicCRS
      */
-    public GeographicCRS geographic() {
+    public synchronized GeographicCRS geographic() {
         GeographicCRS object = geographic(cached);
         if (object == null) {
             final GeodeticAuthorityFactory factory = factory();
             if (factory != null) try {
-                // Synchronization provided by the cache of the factory.
                 cached = object = 
factory.createGeographicCRS(String.valueOf(geographic));
                 return object;
             } catch (FactoryException e) {
@@ -658,20 +658,13 @@ public enum CommonCRS {
              * We will arbitrarily create this CS only for the most frequently 
created CRS,
              * and share that CS instance for all other constants.
              */
-            EllipsoidalCS cs = null;
+            final EllipsoidalCS cs;
             if (this != DEFAULT) {
                 cs = DEFAULT.geographic().getCoordinateSystem();
+            } else {
+                cs = (EllipsoidalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.ELLIPSOIDAL_2D, 
true);
             }
-            synchronized (this) {
-                object = geographic(cached);
-                if (object == null) {
-                    if (cs == null) {
-                        cs = (EllipsoidalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.ELLIPSOIDAL_2D, 
true);
-                    }
-                    object = 
StandardDefinitions.createGeographicCRS(geographic, frame, cs);
-                    cached = object;
-                }
-            }
+            cached = object = 
StandardDefinitions.createGeographicCRS(geographic, frame, cs);
         }
         return object;
     }
@@ -700,15 +693,12 @@ public enum CommonCRS {
      * @see CRS#forCode(String)
      * @see DefaultGeographicCRS
      */
-    public GeographicCRS geographic3D() {
-        GeographicCRS object = cachedGeo3D;
-        if (object == null) {
+    public synchronized GeographicCRS geographic3D() {
+        if (cachedGeo3D == null) {
             if (geo3D != 0) {
                 final GeodeticAuthorityFactory factory = factory();
                 if (factory != null) try {
-                    // Synchronization provided by the cache of the factory.
-                    cachedGeo3D = object = 
factory.createGeographicCRS(String.valueOf(geo3D));
-                    return object;
+                    return cachedGeo3D = 
factory.createGeographicCRS(String.valueOf(geo3D));
                 } catch (FactoryException e) {
                     failure(this, "geographic3D", e, geo3D);
                 }
@@ -719,23 +709,16 @@ public enum CommonCRS {
              * We will arbitrarily create this CS only for the most frequently 
created CRS,
              * and share that CS instance for all other constants.
              */
-            EllipsoidalCS cs = null;
+            final EllipsoidalCS cs;
             if (this != DEFAULT) {
                 cs = DEFAULT.geographic3D().getCoordinateSystem();
+            } else {
+                cs = (EllipsoidalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.ELLIPSOIDAL_3D, 
true);
             }
-            synchronized (this) {
-                object = cachedGeo3D;
-                if (object == null) {
-                    if (cs == null) {
-                        cs = (EllipsoidalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.ELLIPSOIDAL_3D, 
true);
-                    }
-                    // Use same name and datum than the geographic CRS.
-                    object = new DefaultGeographicCRS(properties(base, geo3D), 
base.getDatum(), base.getDatumEnsemble(), cs);
-                    cachedGeo3D = object;
-                }
-            }
+            // Use same name and datum than the geographic CRS.
+            cachedGeo3D = new DefaultGeographicCRS(properties(base, geo3D), 
base.getDatum(), base.getDatumEnsemble(), cs);
         }
-        return object;
+        return cachedGeo3D;
     }
 
     /**
@@ -761,15 +744,12 @@ public enum CommonCRS {
      * @see CRS#forCode(String)
      * @see DefaultGeocentricCRS
      */
-    public GeodeticCRS geocentric() {
-        GeodeticCRS object = cachedGeocentric;
-        if (object == null) {
+    public synchronized GeodeticCRS geocentric() {
+        if (cachedGeocentric == null) {
             if (geocentric != 0) {
                 final GeodeticAuthorityFactory factory = factory();
                 if (factory != null) try {
-                    // Synchronization provided by the cache of the factory.
-                    cachedGeocentric = object = 
factory.createGeodeticCRS(String.valueOf(geocentric));
-                    return object;
+                    return cachedGeocentric = 
factory.createGeodeticCRS(String.valueOf(geocentric));
                 } catch (FactoryException e) {
                     failure(this, "geocentric", e, geocentric);
                 }
@@ -781,22 +761,15 @@ public enum CommonCRS {
              * We will arbitrarily create this CS only for the most frequently 
created CRS,
              * and share that CS instance for all other constants.
              */
-            CartesianCS cs = null;
+            final CartesianCS cs;
             if (this != DEFAULT) {
                 cs = (CartesianCS) DEFAULT.geocentric().getCoordinateSystem();
+            } else {
+                cs = (CartesianCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.EARTH_CENTRED, 
true);
             }
-            synchronized (this) {
-                object = cachedGeocentric;
-                if (object == null) {
-                    if (cs == null) {
-                        cs = (CartesianCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.EARTH_CENTRED, 
true);
-                    }
-                    object = new DefaultGeocentricCRS(properties(base, 
geocentric), base.getDatum(), base.getDatumEnsemble(), cs);
-                    cachedGeocentric = object;
-                }
-            }
+            cachedGeocentric = new DefaultGeocentricCRS(properties(base, 
geocentric), base.getDatum(), base.getDatumEnsemble(), cs);
         }
-        return object;
+        return cachedGeocentric;
     }
 
     /**
@@ -814,9 +787,8 @@ public enum CommonCRS {
      *
      * @since 0.7
      */
-    public GeodeticCRS spherical() {
-        GeodeticCRS object = cachedSpherical;
-        if (object == null) {
+    public synchronized GeodeticCRS spherical() {
+        if (cachedSpherical == null) {
             /*
              * All constants defined in this enumeration use the same 
coordinate system, EPSG:6404.
              * We will arbitrarily create this CS only for the most frequently 
created CRS,
@@ -828,29 +800,22 @@ public enum CommonCRS {
             } else {
                 final GeodeticAuthorityFactory factory = factory();
                 if (factory != null) try {
-                    // Synchronization provided by the cache of the factory.
                     cs = 
factory.createSphericalCS(Short.toString(StandardDefinitions.SPHERICAL));
                 } catch (FactoryException e) {
                     failure(this, "spherical", e, 
StandardDefinitions.SPHERICAL);
                 }
+                if (cs == null) {
+                    cs = (SphericalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.SPHERICAL, true);
+                }
             }
             // Use same name and datum than the geographic CRS.
             final GeographicCRS base = geographic();
-            synchronized (this) {
-                object = cachedSpherical;
-                if (object == null) {
-                    if (cs == null) {
-                        cs = (SphericalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.SPHERICAL, true);
-                    }
-                    object = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()),
-                                                      base.getDatum(),
-                                                      base.getDatumEnsemble(),
-                                                      cs);
-                    cachedSpherical = object;
-                }
-            }
+            cachedSpherical = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()),
+                                              base.getDatum(),
+                                              base.getDatumEnsemble(),
+                                              cs);
         }
-        return object;
+        return cachedSpherical;
     }
 
     /**
@@ -876,12 +841,11 @@ public enum CommonCRS {
      * @see #forDatum(CoordinateReferenceSystem)
      * @see org.apache.sis.referencing.datum.DefaultGeodeticDatum
      */
-    public GeodeticDatum datum() {
+    public synchronized GeodeticDatum datum() {
         GeodeticDatum object = datum(cached);
         if (object == null) {
             final GeodeticAuthorityFactory factory = factory();
             if (factory != null) try {
-                // Synchronization provided by the cache of the factory.
                 cached = object = 
factory.createGeodeticDatum(String.valueOf(datum));
                 return object;
             } catch (FactoryException e) {
@@ -889,13 +853,7 @@ public enum CommonCRS {
             }
             final var ei = ellipsoid();
             final var pm = primeMeridian();
-            synchronized (this) {
-                object = datum(cached);
-                if (object == null) {
-                    object = StandardDefinitions.createGeodeticDatum(datum, 
ei, pm);
-                    cached = object;
-                }
-            }
+            cached = object = StandardDefinitions.createGeodeticDatum(datum, 
ei, pm);
         }
         return object;
     }
@@ -931,28 +889,22 @@ public enum CommonCRS {
      *
      * @see org.apache.sis.referencing.datum.DefaultEllipsoid
      */
-    public Ellipsoid ellipsoid() {
+    public synchronized Ellipsoid ellipsoid() {
         Ellipsoid object = ellipsoid(cached);
         if (object == null) {
             if (this == NAD83) {
-                cached = object = ETRS89.ellipsoid();       // Share the same 
instance for NAD83 and ETRS89.
-                return object;
-            }
-            final GeodeticAuthorityFactory factory = factory();
-            if (factory != null) try {
-                // Synchronization provided by the cache of the factory.
-                cached = object = 
factory.createEllipsoid(String.valueOf(ellipsoid));
-                return object;
-            } catch (FactoryException e) {
-                failure(this, "ellipsoid", e, ellipsoid);
-            }
-            synchronized (this) {
-                object = ellipsoid(cached);
-                if (object == null) {
-                    object = StandardDefinitions.createEllipsoid(ellipsoid);
-                    cached = object;
+                object = ETRS89.ellipsoid();       // Share the same instance 
for NAD83 and ETRS89.
+            } else {
+                final GeodeticAuthorityFactory factory = factory();
+                if (factory != null) try {
+                    cached = object = 
factory.createEllipsoid(String.valueOf(ellipsoid));
+                    return object;
+                } catch (FactoryException e) {
+                    failure(this, "ellipsoid", e, ellipsoid);
                 }
+                object = StandardDefinitions.createEllipsoid(ellipsoid);
             }
+            cached = object;
         }
         return object;
     }
@@ -972,28 +924,22 @@ public enum CommonCRS {
      *
      * @see org.apache.sis.referencing.datum.DefaultPrimeMeridian
      */
-    public PrimeMeridian primeMeridian() {
+    public synchronized PrimeMeridian primeMeridian() {
         PrimeMeridian object = primeMeridian(cached);
         if (object == null) {
             if (this != DEFAULT) {
-                cached = object = DEFAULT.primeMeridian();          // Share 
the same instance for all constants.
-                return object;
-            }
-            final GeodeticAuthorityFactory factory = factory();
-            if (factory != null) try {
-                // Synchronization provided by the cache of the factory.
-                cached = object = 
factory.createPrimeMeridian(StandardDefinitions.GREENWICH);
-                return object;
-            } catch (FactoryException e) {
-                failure(this, "primeMeridian", e, Constants.EPSG_GREENWICH);
-            }
-            synchronized (this) {
-                object = primeMeridian(cached);
-                if (object == null) {
-                    object = StandardDefinitions.primeMeridian();
-                    cached = object;
+                object = DEFAULT.primeMeridian();          // Share the same 
instance for all constants.
+            } else {
+                final GeodeticAuthorityFactory factory = factory();
+                if (factory != null) try {
+                    cached = object = 
factory.createPrimeMeridian(StandardDefinitions.GREENWICH);
+                    return object;
+                } catch (FactoryException e) {
+                    failure(this, "primeMeridian", e, 
Constants.EPSG_GREENWICH);
                 }
+                object = StandardDefinitions.primeMeridian();
             }
+            cached = object;
         }
         return object;
     }
@@ -1366,7 +1312,7 @@ public enum CommonCRS {
          * on which method has been invoked. The kind of object stored in this 
field may change during the
          * application execution.
          */
-        private transient volatile IdentifiedObject cached;
+        private transient IdentifiedObject cached;
 
         /**
          * Creates a new enumeration value of the given name.
@@ -1420,31 +1366,25 @@ public enum CommonCRS {
          *
          * @see DefaultVerticalCRS
          */
-        public VerticalCRS crs() {
+        public synchronized VerticalCRS crs() {
             VerticalCRS object = crs(cached);
             if (object == null) {
                 if (isEPSG) {
                     final GeodeticAuthorityFactory factory = factory();
                     if (factory != null) try {
-                        // Synchronization provided by the cache of the 
factory.
                         cached = object = 
factory.createVerticalCRS(String.valueOf(crs));
                         return object;
                     } catch (FactoryException e) {
                         failure(this, "crs", e, crs);
                     }
                 }
-                synchronized (this) {
-                    object = crs(cached);
-                    if (object == null) {
-                        if (isEPSG) {
-                            object = 
StandardDefinitions.createVerticalCRS(crs, datum());
-                        } else {
-                            final VerticalCS cs = cs();
-                            object = new 
DefaultVerticalCRS(IdentifiedObjects.getProperties(cs, exclude()), datum(), 
null, cs);
-                        }
-                        cached = object;
-                    }
+                if (isEPSG) {
+                    object = StandardDefinitions.createVerticalCRS(crs, 
datum());
+                } else {
+                    final VerticalCS cs = cs();
+                    object = new 
DefaultVerticalCRS(IdentifiedObjects.getProperties(cs, exclude()), datum(), 
null, cs);
                 }
+                cached = object;
             }
             return object;
         }
@@ -1488,39 +1428,33 @@ public enum CommonCRS {
          *
          * @see DefaultVerticalDatum
          */
-        public VerticalDatum datum() {
+        public synchronized VerticalDatum datum() {
             VerticalDatum object = datum(cached);
             if (object == null) {
                 if (isEPSG) {
                     final GeodeticAuthorityFactory factory = factory();
                     if (factory != null) try {
-                        // Synchronization provided by the cache of the 
factory.
                         cached = object = 
factory.createVerticalDatum(String.valueOf(datum));
                         return object;
                     } catch (FactoryException e) {
                         failure(this, "datum", e, datum);
                     }
                 }
-                synchronized (this) {
-                    object = datum(cached);
-                    if (object == null) {
-                        if (isEPSG) {
-                            object = 
StandardDefinitions.createVerticalDatum(datum);
-                        } else {
-                            /*
-                             * All cases where the first constructor argument 
is `false`, currently BAROMETRIC and
-                             * ELLIPSOIDAL. The way to construct the 
ellipsoidal pseudo-method shall be equivalent
-                             * to a call to `VerticalDatumTypes.ellipsoidal()`.
-                             */
-                            RealizationMethod method = null;
-                            if (this != OTHER_SURFACE) {
-                                method = RealizationMethod.valueOf(name());
-                            }
-                            object = new 
DefaultVerticalDatum(properties(datum), method);
-                        }
-                        cached = object;
+                if (isEPSG) {
+                    object = StandardDefinitions.createVerticalDatum(datum);
+                } else {
+                    /*
+                     * All cases where the first constructor argument is 
`false`, currently BAROMETRIC and
+                     * ELLIPSOIDAL. The way to construct the ellipsoidal 
pseudo-method shall be equivalent
+                     * to a call to `VerticalDatumTypes.ellipsoidal()`.
+                     */
+                    RealizationMethod method = null;
+                    if (this != OTHER_SURFACE) {
+                        method = RealizationMethod.valueOf(name());
                     }
+                    object = new DefaultVerticalDatum(properties(datum), 
method);
                 }
+                cached = object;
             }
             return object;
         }
@@ -1691,7 +1625,7 @@ public enum CommonCRS {
          * on which method has been invoked. The kind of object stored in this 
field may change during the
          * application execution.
          */
-        private transient volatile IdentifiedObject cached;
+        private transient IdentifiedObject cached;
 
         /**
          * Creates a new enumeration value of the given name with time counted 
since the given epoch.
@@ -1796,26 +1730,20 @@ public enum CommonCRS {
          *
          * @see DefaultTemporalCRS
          */
-        public TemporalCRS crs() {
+        public synchronized TemporalCRS crs() {
             TemporalCRS object = crs(cached);
             if (object == null) {
-                synchronized (this) {
-                    object = crs(cached);
-                    if (object == null) {
-                        final TemporalDatum datum = datum();
-                        final Map<String,?> source;
-                        if (this == JAVA) {
-                            source = 
properties(Vocabulary.formatInternational(key, "Java"));
-                        } else {
-                            source = IdentifiedObjects.getProperties(datum, 
exclude());
-                        }
-                        final Map<String,Object> properties = new 
HashMap<>(source);
-                        properties.put(TemporalCRS.IDENTIFIERS_KEY,
-                                new NamedIdentifier(isOGC ? Citations.OGC : 
Citations.SIS, identifier));
-                        object = new DefaultTemporalCRS(properties, datum, 
null, cs());
-                        cached = object;
-                    }
+                final TemporalDatum datum = datum();
+                final Map<String,?> source;
+                if (this == JAVA) {
+                    source = properties(Vocabulary.formatInternational(key, 
"Java"));
+                } else {
+                    source = IdentifiedObjects.getProperties(datum, exclude());
                 }
+                final Map<String,Object> properties = new HashMap<>(source);
+                properties.put(TemporalCRS.IDENTIFIERS_KEY,
+                        new NamedIdentifier(isOGC ? Citations.OGC : 
Citations.SIS, identifier));
+                cached = object = new DefaultTemporalCRS(properties, datum, 
null, cs());
             }
             return object;
         }
@@ -1868,26 +1796,20 @@ public enum CommonCRS {
          *
          * @see DefaultTemporalDatum
          */
-        public TemporalDatum datum() {
+        public synchronized TemporalDatum datum() {
             TemporalDatum object = datum(cached);
             if (object == null) {
                 if (this == UNIX) {
                     cached = object = JAVA.datum();         // Share the same 
instance for UNIX and JAVA.
                     return object;
                 }
-                synchronized (this) {
-                    object = datum(cached);
-                    if (object == null) {
-                        final Map<String,?> properties;
-                        if (key == Vocabulary.Keys.Time_1) {
-                            properties = 
properties(Vocabulary.formatInternational(key, "Unix/POSIX"));
-                        } else {
-                            properties = properties(key);
-                        }
-                        object = new DefaultTemporalDatum(properties, 
Instant.ofEpochSecond(epoch));
-                        cached = object;
-                    }
+                final Map<String,?> properties;
+                if (key == Vocabulary.Keys.Time_1) {
+                    properties = 
properties(Vocabulary.formatInternational(key, "Unix/POSIX"));
+                } else {
+                    properties = properties(key);
                 }
+                cached = object = new DefaultTemporalDatum(properties, 
Instant.ofEpochSecond(epoch));
             }
             return object;
         }
@@ -2152,11 +2074,25 @@ public enum CommonCRS {
      * After invoking this method, the caller will fallback on hard-coded 
values.
      */
     private static void failure(final Object caller, final String method, 
final FactoryException e, final int code) {
+        final LogRecord record;
         String message = 
Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, 
(Constants.EPSG + ':') + code);
-        message = Exceptions.formatChainedMessages(null, message, e);
-        final var record = new LogRecord(Level.WARNING, message);
-        if (!(e instanceof UnavailableFactoryException && 
AuthorityFactories.isUnavailable((UnavailableFactoryException) e))) {
+        if (e instanceof UnavailableFactoryException && 
!AuthorityFactories.isUnavailable((UnavailableFactoryException) e)) {
+            /*
+             * This exception may be normal if the user didn't installed the 
EPSG geodetic dataset.
+             * However, we use the `WARNING` level anyway because this 
exception happens only when
+             * user specified some from of data source, e.g with the SIS_DATA 
environment variable,
+             * in which case she may want to know that it didn't worked. This 
exception should not
+             * occur when the user did not configured anything.
+             *
+             * This exception usually happens only once, because the failure 
should be recorded in the
+             * `AuthorityFactories.EPSG` field.  This exception may 
nevertheless happen more than once
+             * if there is a race condition (many calls to `CommonCRS` in 
different threads before the
+             * failure get recorded). It happens during tests.
+             */
+            record = new LogRecord(Level.WARNING, 
Exceptions.formatChainedMessages(null, message, e));
+        } else {
             // Append the stack trace only if the exception is for a reason 
different than unavailable factory.
+            record = new LogRecord(Level.WARNING, message);
             record.setThrown(e);
         }
         Logging.completeAndLog(AuthorityFactories.LOGGER, caller.getClass(), 
method, record);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index cb507103ed..95dd062d1c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@ -752,7 +752,7 @@ public abstract class ConcurrentAuthorityFactory<DAO 
extends GeodeticAuthorityFa
              * Do not log the stack trace if we failed because of 
UnavailableFactoryException since it may be
              * normal (the EPSG geodetic dataset is optional, even if strongly 
recommended).
              */
-            final LogRecord record = new LogRecord(c == null ? Level.WARNING : 
Level.FINE, e.getLocalizedMessage());
+            final var record = new LogRecord(c == null ? Level.WARNING : 
Level.FINE, e.getLocalizedMessage());
             if (!(e instanceof UnavailableFactoryException)) {
                 record.setThrown(e);
             }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index 4f179926bf..035277476d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@ -583,12 +583,14 @@ public class IdentifiedObjectFinder {
      * Invoked when an exception occurred during the creation of a candidate 
from a code.
      */
     private static void exceptionOccurred(final FactoryException exception) {
-        /*
-         * use `getMessage()` instead of `getLocalizedMessage()` for
-         * giving preference to the locale of system administrator.
-         */
-        Logging.completeAndLog(GeodeticAuthorityFactory.LOGGER, 
IdentifiedObjectFinder.class,
-                               "find", new LogRecord(Level.FINER, 
exception.getMessage()));
+        if (GeodeticAuthorityFactory.LOGGER.isLoggable(Level.FINER)) {
+            /*
+             * use `getMessage()` instead of `getLocalizedMessage()` for
+             * giving preference to the locale of system administrator.
+             */
+            Logging.completeAndLog(GeodeticAuthorityFactory.LOGGER, 
IdentifiedObjectFinder.class,
+                                   "find", new LogRecord(Level.FINER, 
exception.getMessage()));
+        }
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
index 244b0bf26b..50fb583cb1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
@@ -246,10 +246,10 @@ public class EPSGFactory extends 
ConcurrentAuthorityFactory<EPSGDataAccess> impl
      *                     or {@code null} for the default values.
      * @throws ClassCastException if a property value is not of the expected 
class.
      * @throws IllegalArgumentException if a property value is invalid.
-     * @throws FactoryException if an error occurred while creating the EPSG 
factory.
+     * @throws UnavailableFactoryException if an error occurred while creating 
the EPSG factory.
      */
     @SuppressWarnings("this-escape")    // The invoked method does not store 
`this` and is not overrideable.
-    public EPSGFactory(Map<String,?> properties) throws FactoryException {
+    public EPSGFactory(Map<String,?> properties) throws 
UnavailableFactoryException {
         super(EPSGDataAccess.class);
         if (properties == null) {
             properties = Map.of();
@@ -266,12 +266,13 @@ public class EPSGFactory extends 
ConcurrentAuthorityFactory<EPSGDataAccess> impl
         this.locale = locale;
         if (ds == null) try {
             ds = Initializer.getDataSource();
-            if (ds == null) {
-                throw new 
UnavailableFactoryException(String.valueOf(Initializer.unspecified(locale, 
false)));
-            }
         } catch (Exception e) {
             throw new UnavailableFactoryException(canNotUse(e), e);
         }
+        if (ds == null) {
+            // Must be outside the above `try` block.
+            throw new 
UnavailableFactoryException(String.valueOf(Initializer.unspecified(locale, 
false)));
+        }
         final var c = new ReferencingFactoryContainer(properties);
         dataSource   = ds;
         nameFactory  = c.getNameFactory();
@@ -406,7 +407,7 @@ public class EPSGFactory extends 
ConcurrentAuthorityFactory<EPSGDataAccess> impl
                     }
                 }
             } catch (IOException | SQLException e) {
-                message = installer.failure(locale, e);
+                message = installer.failure(locale);
                 failure = e;
             }
         } catch (SQLException e) {
@@ -418,7 +419,7 @@ public class EPSGFactory extends 
ConcurrentAuthorityFactory<EPSGDataAccess> impl
              * Derby sometimes 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.
              */
-            UnavailableFactoryException exception = new 
UnavailableFactoryException(message, Exceptions.unwrap(failure));
+            var exception = new UnavailableFactoryException(message, 
Exceptions.unwrap(failure));
             exception.setUnavailableFactory(this);
             throw exception;
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
index dfd4171ba0..23f169f240 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
@@ -27,7 +27,6 @@ import java.sql.SQLException;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import org.apache.sis.util.StringBuilders;
-import org.apache.sis.util.Exceptions;
 import org.apache.sis.metadata.sql.privy.ScriptRunner;
 import org.apache.sis.metadata.sql.privy.SQLUtilities;
 import org.apache.sis.util.privy.Constants;
@@ -297,12 +296,12 @@ final class EPSGInstaller extends ScriptRunner {
      * caught an exception. This method completes the exception message with 
the file name and line number where the
      * error occurred, if such information is available.
      */
-    final String failure(final Locale locale, final Exception cause) {
+    final String failure(final Locale locale) {
         String message = 
Messages.forLocale(locale).getString(Messages.Keys.CanNotCreateSchema_1, EPSG);
         String status = status(locale);
         if (status != null) {
             message = message + ' ' + status;
         }
-        return Exceptions.formatChainedMessages(locale, message, cause);
+        return message;
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
index 7c6eb1fd3a..9ff0ed9f21 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
@@ -62,6 +62,7 @@ import org.apache.sis.metadata.privy.ReferencingServices;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.system.Modules;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.privy.Constants;
@@ -504,7 +505,7 @@ public final class ServicesForMetadata extends 
ReferencingServices {
                     authority = 
CRS.getAuthorityFactory(Constants.EPSG).getAuthority();
                 } catch (FactoryException e) {
                     final String msg = Exceptions.getLocalizedMessage(e, 
locale);
-                    return (msg != null) ? msg : e.toString();
+                    return (msg != null) ? msg : Classes.getShortClassName(e);
                 }
                 if (authority != null) {
                     final OnLineFunction f = 
OnLineFunction.valueOf(CONNECTION);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index 7ead80c4f7..cfe429ed60 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -26,6 +26,7 @@ import java.text.NumberFormat;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.io.TableAppender;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.resources.Errors;
@@ -435,7 +436,7 @@ final class ProjectedTransformTry implements 
Comparable<ProjectedTransformTry>,
         if (error != null) {
             message = Exceptions.getLocalizedMessage(error, locale);
             if (message == null) {
-                message = error.getClass().getSimpleName();
+                message = Classes.getShortClassName(error);
             }
         } else if (correlation > 0) {
             if (nf == null) {
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
index 22b38cc339..cf0ae605dd 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
@@ -43,7 +43,7 @@ public final class DirectPosition1DTest extends TestCase {
      */
     @Test
     public void testWktFormatting() {
-        final DirectPosition1D position = new DirectPosition1D(8.5);
+        final var position = new DirectPosition1D(8.5);
         assertEquals("POINT(8.5)", position.toString());
         validate(position);
     }
@@ -53,7 +53,7 @@ public final class DirectPosition1DTest extends TestCase {
      */
     @Test
     public void testWktParsing() {
-        final DirectPosition1D position = new DirectPosition1D("POINT(8)");
+        final var position = new DirectPosition1D("POINT(8)");
         assertEquals("POINT(8)", position.toString());
         validate(position);
     }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
index 891c536b1c..3dd75cd7ed 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
@@ -46,7 +46,6 @@ import javax.imageio.stream.ImageInputStream;
 import javax.xml.stream.Location;
 import javax.xml.stream.XMLStreamReader;
 import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.privy.Constants;
@@ -415,8 +414,8 @@ public final class IOUtilities extends Static {
         } catch (MalformedURLException cause) {
             ex = cause;
         } catch (IllegalArgumentException | URISyntaxException cause) {
-            ex = (MalformedURLException) new 
MalformedURLException(Exceptions.formatChainedMessages(null,
-                    Errors.format(Errors.Keys.IllegalArgumentValue_2, "path", 
path), cause)).initCause(cause);
+            ex = new 
MalformedURLException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "path", 
path));
+            ex.initCause(cause);
         }
         if (suppressed != null) {
             ex.addSuppressed(suppressed);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
index 9e57f77cd6..01d7a950c3 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
@@ -482,14 +482,16 @@ public class StoreListeners implements Localized {
         if (exception == null) {
             ArgumentChecks.ensureNonEmpty("message", message);
         } else {
-            message = Exceptions.formatChainedMessages(getLocale(), message, 
exception);
             if (message == null) {
-                message = exception.toString();
+                message = Exceptions.getLocalizedMessage(exception, 
getLocale());
+                if (message == null) {
+                    message = Classes.getShortClassName(exception);
+                }
             }
         }
-        final LogRecord record = new LogRecord(level, message);
+        final var record = new LogRecord(level, message);
         if (exception == null) {
-           
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream)
 -> stream.filter(
+            
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream)
 -> stream.filter(
                    (frame) -> setPublicSource(record, 
frame.getDeclaringClass(), frame.getMethodName())).findFirst());
          } else try {
             record.setThrown(exception);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/DataDirectory.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/DataDirectory.java
index b38f5c23a1..ce5d59171b 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/DataDirectory.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/DataDirectory.java
@@ -116,7 +116,8 @@ public enum DataDirectory {
     private static void warning(final Exception e, final short key, final 
Object... parameters) {
         if (key != lastWarning) {
             lastWarning = key;
-            log(Level.WARNING, e, key, parameters);
+            boolean config = (key == 
Messages.Keys.DataDirectoryNotSpecified_1);
+            log(config ? Level.CONFIG : Level.WARNING, e, key, parameters);
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java
index fed0584a07..fd71139c3a 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java
@@ -111,16 +111,22 @@ public final class Exceptions extends Static {
     }
 
     /**
-     * Returns a string which contains the given message on the first line, 
followed by the
-     * {@linkplain #getLocalizedMessage(Throwable, Locale) localized message} 
of the given exception
-     * on the next line. If the exception has a {@linkplain 
Throwable#getCause() causes}, then
-     * the class name and the localized message of the cause are formatted on 
the next line
-     * and the process is repeated for the whole cause chain, omitting 
duplicated messages.
+     * Returns a string which contains the given message on the first line,
+     * followed by the localized message of the given exception on the next 
line.
+     * If the exception has a {@linkplain Throwable#getCause() causes}, then 
the class name and the
+     * {@linkplain #getLocalizedMessage(Throwable, Locale) localized message} 
of the cause are formatted
+     * on the next line. The process is repeated for the whole cause chain, 
omitting duplicated messages.
+     * This method does not format the stack trace.
      *
-     * <p>{@link SQLException} is handled especially in order to process the
-     * {@linkplain SQLException#getNextException() next exception} instead of 
the cause.</p>
+     * <h4>Special cases</h4>
+     * {@link SQLException} is handled is a special way by giving precedence to
+     * {@link SQLException#getNextException()} over {@link 
Throwable#getCause()}.
      *
-     * <p>This method does not format the stack trace.</p>
+     * <h4>When to use</h4>
+     * This method should not be used when the given exception will be 
reported through, for example,
+     * {@link Throwable#initCause(Throwable)} or {@link 
java.util.logging.LogRecord#setThrown(Throwable)},
+     * because the redundancy may be confusing. This method is rather for 
situations where the exception
+     * will be discarded or hidden.
      *
      * @param  locale  the preferred locale for the exception message, or 
{@code null}.
      * @param  header  the message to insert on the first line, or {@code 
null} if none.
@@ -129,7 +135,7 @@ public final class Exceptions extends Static {
      *         and no exception provide a message.
      */
     public static String formatChainedMessages(final Locale locale, final 
String header, Throwable cause) {
-        final List<String> previousLines = new ArrayList<>();
+        final var previousLines = new ArrayList<String>();
         StringBuilder buffer = null;
         Vocabulary resources = null;
         while (cause != null) {
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Logging.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Logging.java
index c3c68deafd..3387642587 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Logging.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Logging.java
@@ -275,10 +275,12 @@ public final class Logging extends Static {
             buffer.append(": ").append(message);
         }
         message = buffer.toString();
-        message = Exceptions.formatChainedMessages(null, message, error);
-        final LogRecord record = new LogRecord(level, message);
+        final LogRecord record;
         if (level.intValue() >= LEVEL_THRESHOLD_FOR_STACKTRACE) {
+            record = new LogRecord(level, message);
             record.setThrown(error);
+        } else {
+            record = new LogRecord(level, 
Exceptions.formatChainedMessages(null, message, error));
         }
         if (logger == null || classe == null || method == null) {
             logger = inferCaller(logger, classe, method, 
Arrays.stream(error.getStackTrace()), record);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
index 5ae36d1d3d..71eda54802 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -321,15 +321,17 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
                      * different Java implementation, but it doesn't matter 
here since
                      * we use the result only for logging purpose.
                      */
-                    String language = null;
-                    if (locale != null) {
-                        language = locale.getDisplayName(Locale.US);
-                    }
-                    if (Strings.isNullOrEmpty(language)) {
-                        language = "<root>";
+                    if (LOGGER.isLoggable(record.getLevel())) {
+                        String language = null;
+                        if (locale != null) {
+                            language = locale.getDisplayName(Locale.US);
+                        }
+                        if (Strings.isNullOrEmpty(language)) {
+                            language = "<root>";
+                        }
+                        record.setParameters(new String[] {language, 
baseName});
+                        Logging.completeAndLog(LOGGER, 
IndexedResourceBundle.class, methodName, record);
                     }
-                    record.setParameters(new String[] {language, baseName});
-                    Logging.completeAndLog(LOGGER, 
IndexedResourceBundle.class, methodName, record);
                 }
                 this.values = values;
             }
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/Assertions.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/Assertions.java
index aeeec8c5ff..dd883066f4 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/Assertions.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/Assertions.java
@@ -35,7 +35,6 @@ import java.io.ByteArrayOutputStream;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Static;
 
@@ -119,8 +118,8 @@ public final class Assertions extends Static {
                 for (int i=0; i<length; i++) try {
                     assertEqualsIgnoreMetadata(expected[i], actual[i]);
                 } catch (AssertionError e) {
-                    throw new 
AssertionError(Exceptions.formatChainedMessages(null, "Comparison failure at 
index "
-                            + i + " (a " + 
Classes.getShortClassName(actual[i]) + ").", e), e);
+                    throw new AssertionError("Comparison failure at index " + i
+                            + " (a " + Classes.getShortClassName(actual[i]) + 
"): " + e, e);
                 }
                 assertEquals(expected.length, actual.length, "Unexpected array 
length.");
             }
diff --git 
a/incubator/src/org.apache.sis.storage.gsf/main/org/apache/sis/storage/gsf/panama/LibraryLoader.java
 
b/incubator/src/org.apache.sis.storage.gsf/main/org/apache/sis/storage/gsf/panama/LibraryLoader.java
index b42ebfeaaa..1ed6263ae5 100644
--- 
a/incubator/src/org.apache.sis.storage.gsf/main/org/apache/sis/storage/gsf/panama/LibraryLoader.java
+++ 
b/incubator/src/org.apache.sis.storage.gsf/main/org/apache/sis/storage/gsf/panama/LibraryLoader.java
@@ -26,7 +26,6 @@ import java.lang.foreign.SymbolLookup;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.gsf.GSF;
 import org.apache.sis.system.Shutdown;
-import org.apache.sis.util.Exceptions;
 
 
 /**
@@ -117,8 +116,7 @@ public final class LibraryLoader {
         if (error == null) {
             return Optional.empty();
         }
-        String msg = "Cannot initialize the GSF library.";
-        var record = new LogRecord(Level.CONFIG, 
Exceptions.formatChainedMessages(null, msg, error));
+        var record = new LogRecord(Level.CONFIG, "Cannot initialize the GSF 
library.");
         record.setThrown(error);
         return Optional.of(record);
     }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
index 78301442fd..3eec66232c 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -36,6 +36,7 @@ import org.opengis.util.FactoryException;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.logging.Logging;
@@ -481,7 +482,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
                 final Code code = new 
Code(Vocabulary.forLocale(locale).getString(Vocabulary.Keys.Errors));
                 String message = Exceptions.getLocalizedMessage(e, locale);
                 if (message == null) {
-                    message = e.getClass().getSimpleName();
+                    message = Classes.getShortClassName(e);
                 }
                 code.name().set(message);
                 add(code);
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
index f1b949b3a2..b9c6aa63b8 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
@@ -27,7 +27,6 @@ import java.lang.foreign.Arena;
 import java.lang.foreign.SymbolLookup;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.system.Shutdown;
-import org.apache.sis.util.Exceptions;
 
 
 /**
@@ -215,8 +214,7 @@ create: try {
         if (error == null) {
             return Optional.empty();
         }
-        String msg = "Cannot initialize the " + name + " library.";
-        var record = new LogRecord(Level.CONFIG, 
Exceptions.formatChainedMessages(null, msg, error));
+        LogRecord record = 
Resources.forLocale(null).getLogRecord(Level.CONFIG, 
Resources.Keys.CannotInitialize_1, name);
         record.setThrown(error);
         return Optional.of(record);
     }
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.java
index 1ba4bde055..96732637d5 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.java
@@ -59,7 +59,7 @@ public class Resources extends IndexedResourceBundle {
         public static final short AllowedDrivers_1 = 1;
 
         /**
-         * Cannot initialize {0}.
+         * Cannot initialize the {0} native library.
          */
         public static final short CannotInitialize_1 = 2;
 
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.properties
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.properties
index 44d88c2730..77900aec2f 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.properties
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources.properties
@@ -20,7 +20,7 @@
 # For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
 #
 AllowedDrivers_1       = Allowed {0} drivers for opening the file.
-CannotInitialize_1     = Cannot initialize {0}.
+CannotInitialize_1     = Cannot initialize the {0} native library.
 FatalLibraryError_1    = A fatal error occurred and {0} should not be used 
anymore in this JVM.
 FunctionNotFound_1     = A function of the {0} library has not been found.
 LibraryNotFound_1      = The {0} library has not been found.
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources_fr.properties
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources_fr.properties
index dbaf05b743..f3173c097a 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources_fr.properties
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/Resources_fr.properties
@@ -25,7 +25,7 @@
 #   U+00A0 NO-BREAK SPACE         before  :
 #
 AllowedDrivers_1       = Pilotes {0} autoris\u00e9s pour ouvrir le fichier.
-CannotInitialize_1     = Ne peut pas initialiser {0}.
+CannotInitialize_1     = Ne peut pas initialiser la biblioth\u00e8que native 
{0}.
 FatalLibraryError_1    = Une erreur fatale s\u2019est produite et la 
biblioth\u00e8que {0} ne devrait plus \u00eatre utilis\u00e9e dans cette JVM.
 FunctionNotFound_1     = Une fonction de la biblioth\u00e8que {0} n\u2019a pas 
\u00e9t\u00e9 trouv\u00e9e.
 LibraryNotFound_1      = La biblioth\u00e8que {0} n\u2019a pas \u00e9t\u00e9 
trouv\u00e9e.

Reply via email to