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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 908f46ccbe Reduce the risk of exhaustive scan of the EPSG database
when searching for the authority code of a singleton.
908f46ccbe is described below
commit 908f46ccbe04806a21081a65329427523cb2d38e
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Aug 30 10:58:40 2025 +0200
Reduce the risk of exhaustive scan of the EPSG database when searching for
the authority code of a singleton.
---
.../apache/sis/referencing/AuthorityFactories.java | 4 +-
.../factory/ConcurrentAuthorityFactory.java | 122 +++++++++++++++++----
.../factory/IdentifiedObjectFinder.java | 17 ++-
.../referencing/factory/sql/EPSGCodeFinder.java | 2 +-
4 files changed, 113 insertions(+), 32 deletions(-)
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 287bd3fadb..83fd38a632 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
@@ -260,7 +260,7 @@ final class AuthorityFactories<T extends AuthorityFactory>
extends LazySet<T> {
delegate(fallback(e).newIdentifiedObjectFinder());
}
- /** Lookups objects which are approximately equal, using the
fallback if necessary. */
+ /** Looks up objects which are approximately equal, using the
fallback if necessary. */
@Override public Set<IdentifiedObject> find(final IdentifiedObject
object) throws FactoryException {
for (;;) try { // Executed at most twice.
return super.find(object);
@@ -269,7 +269,7 @@ final class AuthorityFactories<T extends AuthorityFactory>
extends LazySet<T> {
}
}
- /** Lookups an object which is approximately equal, using the
fallback if necessary. */
+ /** Looks up an object which is approximately equal, using the
fallback if necessary. */
@Override public IdentifiedObject findSingleton(final
IdentifiedObject object) throws FactoryException {
for (;;) try { // Executed at most twice.
return super.findSingleton(object);
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 af6b17c7c0..62f0b9b3f7 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
@@ -1850,6 +1850,13 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
super(factory);
}
+ /**
+ * Returns the factory given at construction time.
+ */
+ private ConcurrentAuthorityFactory<?> factory() {
+ return (ConcurrentAuthorityFactory<?>) factory;
+ }
+
/**
* Acquires a new {@linkplain #finder}.
* The {@link #release()} method must be invoked in a {@code finally}
block after the call to {@code acquire}.
@@ -1868,7 +1875,7 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
assert Thread.holdsLock(this);
assert (acquireCount == 0) == (finder == null) : acquireCount;
if (acquireCount == 0) {
- final GeodeticAuthorityFactory delegate =
((ConcurrentAuthorityFactory<?>) factory).getDataAccess();
+ final GeodeticAuthorityFactory delegate =
factory().getDataAccess();
/*
* Set `acquireCount` only after we succeed in fetching the
factory, and before any operation on it.
* The intent is to get ConcurrentAuthorityFactory.release()
invoked if and only if the getDataAccess()
@@ -1893,7 +1900,7 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
}
if (--acquireCount == 0) {
finder = null;
- ((ConcurrentAuthorityFactory<?>) factory).release(null, null,
null);
+ factory().release(null, null, null);
}
}
@@ -1911,36 +1918,79 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
}
}
+ /**
+ * Returns the index in the cached {@code Set<IdentifiedObject>[]}
array for a result using the given finder.
+ * The index depends on the finder configuration. The argument should
be either {@link #finder} if non-null,
+ * or {@code this} otherwise.
+ */
+ private static int index(final IdentifiedObjectFinder finder) {
+ int index = finder.getSearchDomain().ordinal();
+ if (finder.isIgnoringAxes()) index += DOMAIN_COUNT;
+ return index;
+ }
+
/**
* Returns the cached value for the given object, or {@code null} if
none.
+ * This is checked by {@link #find(IdentifiedObject)} before actual
search.
* The returned set (if non-null) is unmodifiable.
+ *
+ * @param object the user-specified object to search.
+ * @return the cached result of the find operation, or {@code null} if
none.
*/
@Override
final Set<IdentifiedObject> getFromCache(final IdentifiedObject
object) {
- final Map<IdentifiedObject, Set<IdentifiedObject>[]> findPool =
((ConcurrentAuthorityFactory<?>) factory).findPool;
+ // `finder` should never be null since this method is not invoked
directly by this Finder.
+ return getFromCache(object, index(finder));
+ }
+
+ /**
+ * Implementation of {@link #getFromCache(IdentifiedObject)} with the
specified index to use in the cache.
+ * The index depends on the finder configuration.
+ *
+ * @param object the user-specified object to search.
+ * @param index value of {@link #index(IdentifiedObjectFinder)} or
custom slot.
+ * @return the cached result of the find operation, or {@code null} if
none.
+ */
+ private Set<IdentifiedObject> getFromCache(final IdentifiedObject
object, final int index) {
+ final Map<IdentifiedObject, Set<IdentifiedObject>[]> findPool =
factory().findPool;
synchronized (findPool) {
final Set<IdentifiedObject>[] entry = findPool.get(object);
if (entry != null) {
- // `finder` may be null if this method is invoked directly
by this Finder.
- return entry[index(finder != null ? finder : this)];
+ return entry[index];
}
}
return null;
}
/**
- * Stores the given result in the cache. This method wraps or copies
the given set
- * in an unmodifiable set and returns the result.
+ * Stores the given result in the cache. This method copies the given
set in a new unmodifiable
+ * set and returns the result. The copy is needed because, with the
current implementation of
+ * <abbr>EPSG</abbr> factory, {@code result} may be a lazy set with a
connection to the database.
+ *
+ * @param object the user-specified object which was searched.
+ * @param result the search result. It will be copied.
+ * @return a set with the same content as {@code result}.
*/
@Override
- final Set<IdentifiedObject> cache(final IdentifiedObject object,
Set<IdentifiedObject> result) {
- final Map<IdentifiedObject, Set<IdentifiedObject>[]> findPool =
((ConcurrentAuthorityFactory<?>) factory).findPool;
- result = CollectionsExt.unmodifiableOrCopy(result);
+ final Set<IdentifiedObject> cache(final IdentifiedObject object, final
Set<IdentifiedObject> result) {
+ // `finder` should never be null since this method is not invoked
directly by this Finder.
+ return cache(object, CollectionsExt.copyPreserveOrder(result),
index(finder));
+ }
+
+ /**
+ * Implementation of {@link #cache(IdentifiedObject, Set)} with the
specified index to use in the cache.
+ * The index depends on the finder configuration.
+ *
+ * @param object the user-specified object which was searched.
+ * @param result the search result. The copy, if needed, shall be
done by the caller.
+ * @param index value of {@link #index(IdentifiedObjectFinder)} or
custom slot.
+ * @return a set with the same content as {@code result}.
+ */
+ private Set<IdentifiedObject> cache(final IdentifiedObject object,
Set<IdentifiedObject> result, final int index) {
+ final Map<IdentifiedObject, Set<IdentifiedObject>[]> findPool =
factory().findPool;
synchronized (findPool) {
- // `finder` should never be null since this method is not
invoked directly by this Finder.
- final int i = index(finder);
final Set<IdentifiedObject>[] entry =
findPool.computeIfAbsent(object, Finder::createCacheEntry);
- final Set<IdentifiedObject> existing = entry[i];
+ final Set<IdentifiedObject> existing = entry[index];
if (existing != null) {
return existing;
}
@@ -1950,36 +2000,60 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
break;
}
}
- entry[i] = result;
+ entry[index] = result;
}
return result;
}
/**
* Creates an initially empty cache entry for the given object.
+ * Used in lambda expression and defined as a separated method because
of generic type.
+ * The {@code object} argument is present only for having the required
method signature.
+ *
+ * @param object the user-specified object which was searched.
+ * @return a new array to use as a cache for the specified object.
*/
@SuppressWarnings({"unchecked", "rawtypes"}) // Generic
array creation.
private static Set<IdentifiedObject>[]
createCacheEntry(IdentifiedObject object) {
- return new Set[DOMAIN_COUNT * 2];
+ return new Set[DOMAIN_COUNT * 3];
}
/**
- * Returns the index in the cached {@code Set<IdentifiedObject>[]}
array
- * for a result using the given finder.
+ * Looks up an object from this authority factory which is
approximately equal to the specified object.
+ * The default implementation performs the same lookup as the Data
Access Object and caches the result.
+ *
+ * @param object the object looked up.
+ * @return the identified object, or {@code null} if none or ambiguous.
*/
- private static int index(final IdentifiedObjectFinder finder) {
- int i = finder.getSearchDomain().ordinal();
- if (finder.isIgnoringAxes()) i += DOMAIN_COUNT;
- return i;
+ @Override
+ public IdentifiedObject findSingleton(final IdentifiedObject object)
throws FactoryException {
+ final int index = index(this) + 2*DOMAIN_COUNT;
+ Set<IdentifiedObject> result = getFromCache(object, index);
+ if (result == null) {
+ synchronized (this) {
+ try {
+ acquire();
+ result =
CollectionsExt.singletonOrEmpty(finder.findSingleton(object));
+ } finally {
+ release();
+ }
+ }
+ cache(object, result, index);
+ }
+ factory().findPoolLatestQueries.markAsUsed(object);
+ return CollectionsExt.first(result);
}
/**
- * Looks up an object from this authority factory which is
approximately equal to the specified object.
+ * Looks up objects from this authority factory which are
approximately equal to the specified object.
* The default implementation performs the same lookup as the Data
Access Object and caches the result.
+ *
+ * @param object the object looked up.
+ * @return the identified objects, or an empty set if not found.
*/
@Override
public Set<IdentifiedObject> find(final IdentifiedObject object)
throws FactoryException {
- Set<IdentifiedObject> candidate = getFromCache(object);
+ Set<IdentifiedObject> candidate = getFromCache(object,
index(this));
if (candidate == null) {
/*
* Nothing has been found in the cache. Delegates the search
to the Data Access Object.
@@ -2001,7 +2075,7 @@ public abstract class ConcurrentAuthorityFactory<DAO
extends GeodeticAuthorityFa
* We keep a strong reference only for the top-level object, not
for the intermediate searches,
* because strong references to intermediate objects already exist
in the top-level object.
*/
- ((ConcurrentAuthorityFactory<?>)
factory).findPoolLatestQueries.markAsUsed(object);
+ factory().findPoolLatestQueries.markAsUsed(object);
return candidate;
}
}
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 6d950d6db6..6284d56dae 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
@@ -36,6 +36,7 @@ import org.apache.sis.referencing.privy.LazySet;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Disposable;
import org.apache.sis.util.Utilities;
+import org.apache.sis.util.OptionalCandidate;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.logging.Logging;
@@ -304,7 +305,11 @@ public class IdentifiedObjectFinder {
/**
* Returns the cached value for the given object, or {@code null} if none.
+ * This is checked by {@link #find(IdentifiedObject)} before actual search.
* The returned set (if non-null) should be unmodifiable.
+ *
+ * @param object the user-specified object to search.
+ * @return the cached result of the find operation, or {@code null} if
none.
*/
Set<IdentifiedObject> getFromCache(final IdentifiedObject object) {
return (wrapper != null) ? wrapper.getFromCache(object) : null;
@@ -314,7 +319,8 @@ public class IdentifiedObjectFinder {
* Stores the given result in the cache, if any. If this method chooses to
cache the given set,
* then it shall wrap or copy the given set in an unmodifiable set and
returns the result.
*
- * @param result the search result as a modifiable set.
+ * @param object the user-specified object which was searched.
+ * @param result the search result. It will potentially be copied.
* @return a set with the same content as {@code result}.
*/
Set<IdentifiedObject> cache(final IdentifiedObject object,
Set<IdentifiedObject> result) {
@@ -325,7 +331,7 @@ public class IdentifiedObjectFinder {
}
/**
- * Lookups only one object which is approximately equal to the specified
object.
+ * Looks up only one object which is approximately equal to the specified
object.
* This method invokes {@link #find(IdentifiedObject)}, then examine the
returned {@code Set} as below:
*
* <ul>
@@ -341,6 +347,7 @@ public class IdentifiedObjectFinder {
* @return the identified object, or {@code null} if none or ambiguous.
* @throws FactoryException if an error occurred while fetching the
authority code candidates.
*/
+ @OptionalCandidate
public IdentifiedObject findSingleton(final IdentifiedObject object)
throws FactoryException {
/*
* Do not invoke Set.size() because it may be a costly operation if
the subclass
@@ -368,7 +375,7 @@ public class IdentifiedObjectFinder {
}
/**
- * Lookups objects which are approximately equal to the specified object.
+ * Looks up objects which are approximately equal to the specified object.
* This method tries to instantiate objects identified by the {@linkplain
#getCodeCandidates set of candidate codes}
* using the {@linkplain #factory authority factory} specified at
construction time.
* {@link FactoryException}s thrown during object creations are logged and
otherwise ignored.
@@ -785,7 +792,7 @@ public class IdentifiedObjectFinder {
}
/**
- * Lookups objects which are approximately equal to the specified
object.
+ * Looks up objects which are approximately equal to the specified
object.
* The default method implementation delegates the work to the finder
specified by {@link #delegate()}.
*/
@Override
@@ -796,7 +803,7 @@ public class IdentifiedObjectFinder {
}
/**
- * Lookups only one object which is approximately equal to the
specified object.
+ * Looks up only one object which is approximately equal to the
specified object.
* The default method implementation delegates the work to the finder
specified by {@link #delegate()}.
*/
@Override
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index d7a3d64524..ebe9fe21ef 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -108,7 +108,7 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
}
/**
- * Lookups objects which are approximately equal to the specified object.
+ * Looks up objects which are approximately equal to the specified object.
* This method temporarily disables warnings about deprecated objects.
*/
@Override