This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit ee25b55a3f21df6c53d0a239b3590d1b04fe0607
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue May 5 12:28:12 2026 +0200

    Metadata proxy should implement `java.io.Serializable`.
    This is necessary for serializing some CRS objects.
    
    https://issues.apache.org/jira/browse/SIS-632
---
 .../org/apache/sis/metadata/sql/Dispatcher.java    | 33 ++++++++++++++++++++--
 .../org/apache/sis/metadata/sql/MetadataProxy.java | 13 ++++++++-
 .../apache/sis/metadata/sql/MetadataSource.java    | 22 +++++++++------
 .../org/apache/sis/metadata/sql/package-info.java  |  2 +-
 4 files changed, 56 insertions(+), 14 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
index bcd8c733b8..9a8a7bff84 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
@@ -27,6 +27,7 @@ import java.util.NavigableSet;
 import java.util.Queue;
 import java.util.Set;
 import java.util.SortedSet;
+import java.io.NotSerializableException;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.Containers;
@@ -51,7 +52,7 @@ import org.apache.sis.system.Semaphores;
  * Then the information is fetched in the underlying metadata database.
  *
  * <p>There is usually a one-to-one correspondence between invoked methods and 
the columns to be read, but not always.
- * Some method invocations may actually trig a computation using the values of 
other columns. This happen for example
+ * Some method invocations may actually trig a computation using the values of 
other columns. It happens for example
  * when invoking a deprecated method which computes its value from 
non-deprecated methods. Such situations happen in
  * the transition from ISO 19115:2003 to ISO 19115:2014 and may happen again 
in the future as standards are revised.
  * The algorithms are encoded in implementation classes like the ones in 
{@link org.apache.sis.metadata.iso} packages,
@@ -124,6 +125,7 @@ final class Dispatcher implements InvocationHandler {
 
     /**
      * Invoked when any method from a metadata interface is invoked.
+     * Handles also the methods inherited from {@link Object} class.
      *
      * @param  proxy   the object on which the method is invoked.
      * @param  method  the method invoked.
@@ -131,7 +133,7 @@ final class Dispatcher implements InvocationHandler {
      * @return the value to be returned from the public method invoked by the 
method.
      */
     @Override
-    public Object invoke(final Object proxy, final Method method, final 
Object[] args) {
+    public Object invoke(final Object proxy, final Method method, final 
Object[] args) throws Exception {
         final int n = (args != null) ? args.length : 0;
         switch (method.getName()) {
             case "toString": {
@@ -150,6 +152,10 @@ final class Dispatcher implements InvocationHandler {
                 if (n != 1) break;
                 return (args[0] == source) ? identifier : null;
             }
+            case "writeReplace": {
+                if (n != 0) break;
+                return writeReplace(proxy);
+            }
             default: {
                 if (n != 0) break;
                 /*
@@ -223,7 +229,7 @@ final class Dispatcher implements InvocationHandler {
      * <ol>
      *   <li>If the property value is present in the {@linkplain #cache}, the 
cached value.</li>
      *   <li>If the "cache" can compute the value from other property values, 
the result of that computation.
-     *       This case happen mostly for deprecated properties that are 
replaced by one or more newer properties.</li>
+     *       This case happens mostly for deprecated properties that are 
replaced by one or more newer properties.</li>
      *   <li>The value stored in the database. The database is queried only 
once for the requested property
      *       and the result is cached for future reuse.</li>
      * </ol>
@@ -323,6 +329,27 @@ final class Dispatcher implements InvocationHandler {
         return value;
     }
 
+    /**
+     * Copies the content to an ordinary implementation class (plain old 
object).
+     * The returned object should be serializable if allowed by the 
implementation.
+     *
+     * @param  proxy   the object on which the method is invoked.
+     * @return a copy of this metadata object.
+     */
+    private Object writeReplace(final Object proxy) throws 
NotSerializableException {
+        ReflectiveOperationException cause = null;
+        final Class<?> type = proxy.getClass().getInterfaces()[0];
+        final Class<?> impl = source.standard.getImplementation(type);
+        if (impl != null) try {
+            return impl.getDeclaredConstructor(type).newInstance(proxy);
+        } catch (ReflectiveOperationException e) {
+            cause = e;
+        }
+        final var e = new NotSerializableException(type.getCanonicalName());
+        e.initCause(cause);
+        throw e;
+    }
+
     /**
      * Returns the error message for a failure to query the database for the 
property identified by the given method.
      */
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataProxy.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataProxy.java
index c152084509..76a26fd948 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataProxy.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataProxy.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.metadata.sql;
 
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
 
 /**
  * Interface for metadata that are implemented by a proxy class.
@@ -24,10 +27,18 @@ package org.apache.sis.metadata.sql;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-interface MetadataProxy {
+interface MetadataProxy extends Serializable {
     /**
      * Returns the identifier (primary key) of this metadata if it is using 
the given source,
      * or {@code null} otherwise.
      */
     String identifier(MetadataSource source);
+
+    /**
+     * Copies all proxy content to a serializable implementation.
+     *
+     * @return a serializable object with the same content as this proxy.
+     * @throws ObjectStreamException if an error occurred while copying the 
content.
+     */
+    Object writeReplace() throws ObjectStreamException;
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
index 8db4ce631b..a9c7527db7 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
@@ -113,7 +113,7 @@ import org.opengis.util.ControlledVocabulary;
  *
  * @author  Touraïvane (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.7
  * @since   0.8
  */
 public class MetadataSource implements AutoCloseable {
@@ -257,7 +257,7 @@ public class MetadataSource implements AutoCloseable {
      *
      * @see #lookup(Class, String)
      */
-    private final WeakValueHashMap<CacheKey,Object> pool;
+    private final WeakValueHashMap<CacheKey, Object> pool;
 
     /**
      * Some information about last used objects. Cached on assumption that the 
same information
@@ -630,7 +630,7 @@ public class MetadataSource implements AutoCloseable {
      * @throws ClassCastException if the metadata object does not implement a 
metadata interface
      *         of the expected package.
      */
-    final Map<String,Object> asValueMap(final Object metadata) throws 
ClassCastException {
+    final Map<String, Object> asValueMap(final Object metadata) throws 
ClassCastException {
         return standard.asValueMap(metadata, null, NAME_POLICY, 
ValueExistencePolicy.ALL);
     }
 
@@ -676,7 +676,7 @@ public class MetadataSource implements AutoCloseable {
                 identifier = ((Enum<?>) metadata).name();
             } else {
                 final String table;
-                final Map<String,Object> asMap;
+                final Map<String, Object> asMap;
                 try {
                     table = 
getTableName(standard.getInterface(metadata.getClass()));
                     asMap = asValueMap(metadata);
@@ -710,12 +710,12 @@ public class MetadataSource implements AutoCloseable {
      * @return the identifier of the given metadata, or {@code null} if none.
      * @throws SQLException if an error occurred while searching in the 
database.
      */
-    final String search(final String table, Set<String> columns, final 
Map<String,Object> metadata,
+    final String search(final String table, Set<String> columns, final 
Map<String, Object> metadata,
             final Statement stmt, final SQLBuilder helper) throws 
SQLException, FactoryException
     {
         assert Thread.holdsLock(this);
         helper.clear();
-        for (final Map.Entry<String,Object> entry : metadata.entrySet()) {
+        for (final Map.Entry<String, Object> entry : metadata.entrySet()) {
             /*
              * Gets the value and the column where this value is stored. If 
the value is non-null,
              * then the column must exist otherwise the metadata will be 
considered as not found.
@@ -888,14 +888,18 @@ public class MetadataSource implements AutoCloseable {
                 throw new 
MetadataStoreException(Errors.format(Errors.Keys.DatabaseError_2, type, 
identifier), e);
             }
         } else {
-            final CacheKey key = new CacheKey(type, identifier);
+            final var key = new CacheKey(type, identifier);
             /*
              * IMPLEMENTATION NOTE: be careful to not invoke any method that 
may synchronize on `this`
              * inside a block synchronized on `pool` (implicit synchronization 
of `pool` method calls).
              */
             value = pool.get(key);
             if (value == null && type.isInterface()) {
-                final Dispatcher toSearch = new Dispatcher(identifier, this);
+                final var toSearch = new Dispatcher(identifier, this);
+                /*
+                 * IMPLEMENTATION NOTE: declaration order of interfacs matter. 
The `type` must be first,
+                 * because `Dispatcher.writeReplace(proxy)` will look for that 
interface at that location.
+                 */
                 value = Proxy.newProxyInstance(classloader, new Class<?>[] 
{type, MetadataProxy.class}, toSearch);
                 if (verify) try {
                     /*
@@ -1263,7 +1267,7 @@ public class MetadataSource implements AutoCloseable {
              * which need to never fail, otherwise some memory leak could 
occur. Pretend that the message come
              * from closeExpired(), which is the closest we can get to a 
public API.
              */
-            final LogRecord record = new LogRecord(Level.WARNING, 
e.toString());
+            final var record = new LogRecord(Level.WARNING, e.toString());
             record.setThrown(e);
             warning(MetadataSource.class, "closeExpired", record);
         }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
index 30d5b7ae9b..f05e0230c7 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
@@ -42,7 +42,7 @@
  *
  * @author  Touraïvane (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.6
+ * @version 1.7
  *
  * @see org.apache.sis.referencing.factory.sql
  *

Reply via email to