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 *
