This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch 3.7-dev in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit d1a9100eb880706fbbbfcc0305268e4ba0d1d5c3 Author: Ken Hu <[email protected]> AuthorDate: Mon Aug 24 16:00:59 2026 -0700 Allow operators to deny traversal strategies globally Traversal strategies are commonly registered by static initializers, so unregistering one does not reliably prohibit it. A subsequently loaded provider could register the strategy again. Add a permanent deployment-wide denial that removes the strategy from the registry and ignores future registration attempts. Ignoring registration avoids turning static initialization into a startup failure while ensuring the operator's denial wins regardless of class loading order. Assisted-by: Codex:gpt-5.6-sol --- CHANGELOG.asciidoc | 1 + docs/src/reference/the-traversal.asciidoc | 30 ++++++++++- docs/src/upgrade/release-3.7.x.asciidoc | 28 ++++++++++ .../process/traversal/TraversalStrategies.java | 36 ++++++++++++- .../gremlin/process/TraversalStrategiesTest.java | 63 ++++++++++++++++++++++ 5 files changed, 155 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5771275b27..637c0306a1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -46,6 +46,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Fixed a panic in `gremlin-go` `PartitionStrategy` when `ReadPartitions` was left unset. * Fixed `gremlin-python` `ProductiveByStrategy` to pass through the `productiveKeys` argument, which was previously accepted but never serialized to the server. * Deprecated `ProductiveByStrategy` which was introduced as a temporary way to mimic pre-3.5.0 null processing behavior. +* Backported `TraversalStrategy` registration mechanism in `TraversalStrategies` from the 3.8.x line. * Fixed `gremlin-python` GraphBinary serialization of `BigInteger`/`BigDecimal` negative boundary values (e.g. `-129`) that raised `OverflowError`. * Fixed `gremlin-go` GraphBinary serialization of zero `BigInteger`/`BigDecimal` values, which were encoded with zero length and rejected by Java servers. diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 0da3ac8940..8ba4066f1f 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -5728,6 +5728,35 @@ executing a gremlin traversal. Users and providers can add `TraversalStrategy` d following sections detail how traversal strategies are applied and defined and describe a collection of traversal strategies that are generally useful to end-users. +[[traversalstrategy-registration]] +=== Registration + +`TraversalStrategies.GlobalCache` maintains the global name registry for `TraversalStrategy` classes. Graph system +providers can register the strategies cached for a graph or graph computer with `registerStrategies()`. A strategy +that should be available by name without being added to a cached default strategy set can instead be registered with +`registerStrategy()`. + +[source,java] +---- +TraversalStrategies.GlobalCache.registerStrategies(MyGraph.class, traversalStrategies); +TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class); +---- + +Calling `denyStrategy()` unregisters a strategy and permanently prevents subsequent calls to `registerStrategy()` or +`registerStrategies()` from adding it to the registry again. Registry additions for a denied strategy are ignored. +Denial takes precedence regardless of the order in which graph providers register strategies from static initializers. +It does not remove instances from cached graph or graph computer strategy sets, or prevent explicit use of the strategy. + +WARNING: `denyStrategy()` is intended only for graph system providers and deployment operators that must permanently +exclude a strategy from the global name registry. The denial cannot be reversed for the lifetime of +`TraversalStrategies.GlobalCache`. `unregisterStrategy()` should be used when a strategy only needs to be removed from +the name registry and may be registered again later. + +[source,java] +---- +TraversalStrategies.GlobalCache.denyStrategy(MyStrategy.class); +---- + === Application One can explicitly add or remove `TraversalStrategy` strategies on the `GraphTraversalSource` with the `withStrategies()` @@ -6499,4 +6528,3 @@ String noSugarTranslationWithParameters = noSugarTranslatorWithParameters.transl System.out.println(noSugarTranslationWithParameters); // OUTPUT: g.V().range_(0,10).has(_args_0,_args_1,_args_2).limit(2).values(_args_1) ---- - diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc index 38e939150b..fcb76221b7 100644 --- a/docs/src/upgrade/release-3.7.x.asciidoc +++ b/docs/src/upgrade/release-3.7.x.asciidoc @@ -187,6 +187,34 @@ Applications that store or transmit `InetAddress` values via GraphSON (as a vert Gremlin parameter) must use literal IP address strings going forward. Existing serialized data containing hostname strings will fail to deserialize after upgrading and will need to be migrated to literal IP addresses. +=== Upgrading for Providers + +==== Graph System Providers + +===== Traversal Strategy Registry + +TinkerPop 3.7.7 backports the global `TraversalStrategy` registry in `TraversalStrategies.GlobalCache` from the 3.8.x +line. `registerStrategies()` now publishes cached strategy classes by name, while the new `registerStrategy()` and +`unregisterStrategy()` methods allow individual strategy classes to be added to or removed from the registry. + +[source,java] +---- +TraversalStrategies.GlobalCache.registerStrategies(MyGraph.class, traversalStrategies); +TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class); +TraversalStrategies.GlobalCache.unregisterStrategy(MyStrategy.class); +---- + +The new `denyStrategy()` method permanently removes a strategy from the registry and prevents later registration from +restoring it. It does not alter cached graph or graph computer defaults. This deployment-wide registry operation is +intended only for graph system providers and operators. Reversible removal should use `unregisterStrategy()`. + +[source,java] +---- +TraversalStrategies.GlobalCache.denyStrategy(MyStrategy.class); +---- + +See: link:https://tinkerpop.apache.org/docs/3.7.7/reference/#traversalstrategy[TraversalStrategy Reference] + == TinkerPop 3.7.6 *Release Date: April 1, 2026* diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java index d9a9248f46..1f41c86696 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java @@ -263,6 +263,11 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T put(VertexProgramRestrictionStrategy.class.getSimpleName(), VertexProgramRestrictionStrategy.class); }}; + /** + * Fully qualified names of strategies that cannot be added to {@link #GLOBAL_REGISTRY}. + */ + private static final Set<String> DENIED_STRATEGIES = new HashSet<>(); + static { final TraversalStrategies graphStrategies = new DefaultTraversalStrategies(); graphStrategies.addStrategies( @@ -301,6 +306,10 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T * Register a set of strategies for a particular graph or graph computer class. This is typically done by the * graph or graph computer class itself when it is loaded. Strategy names should be globally unique and are * added to the {@link #GLOBAL_REGISTRY} such that duplicates will overwrite the previous registration. + * <p/> + * <strong>Warning:</strong> A strategy denied with {@link #denyStrategy(Class)} is not added to the registry. + * Denial overrides registration, but does not prevent the strategy from being included in the graph or graph + * computer's cached strategy set. */ public static void registerStrategies(final Class graphOrGraphComputerClass, final TraversalStrategies traversalStrategies) { if (Graph.class.isAssignableFrom(graphOrGraphComputerClass)) @@ -311,27 +320,50 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T throw new IllegalArgumentException("The TraversalStrategies.GlobalCache only supports Graph and GraphComputer strategy caching: " + graphOrGraphComputerClass.getCanonicalName()); // add the strategies in the traversalStrategy to the global registry - traversalStrategies.toList().forEach(strategy -> GLOBAL_REGISTRY.put(strategy.getClass().getSimpleName(), strategy.getClass())); + traversalStrategies.toList().forEach(strategy -> registerStrategy(strategy.getClass())); } /** * Registers a strategy by its simple name, but does not cache an instance of it. Choose this method if you * don't want the strategy to be included as part of the default strategy set, but do want it available to * be looked up by name. + * <p/> + * <strong>Warning:</strong> A strategy denied with {@link #denyStrategy(Class)} is not added to the registry. + * Denial overrides this registration. */ public static void registerStrategy(final Class<? extends TraversalStrategy> clazz) { - GLOBAL_REGISTRY.put(clazz.getSimpleName(), clazz); + if (!DENIED_STRATEGIES.contains(clazz.getName())) + GLOBAL_REGISTRY.put(clazz.getSimpleName(), clazz); } /** * Unregisters a strategy by its simple name. If the strategy is not in the registry then it cannot be * referenced by name, which means that it cannot be removed from execution using * {{@link GraphTraversalSource#withoutStrategies(Class[])}}. + * <p/> + * This operation can be reversed by a later registration. Use {@link #denyStrategy(Class)} only when a strategy + * must remain excluded from the registry for the lifetime of this cache. */ public static void unregisterStrategy(final Class<? extends TraversalStrategy> clazz) { GLOBAL_REGISTRY.remove(clazz.getSimpleName()); } + /** + * Unregisters and permanently denies a strategy from being added to the registry. Subsequent calls to + * {@link #registerStrategy(Class)} and {@link #registerStrategies(Class, TraversalStrategies)} do not add the + * denied strategy to the registry. Denial does not remove the strategy from graph or graph computer strategy + * sets held in this cache, or otherwise prevent it from being used explicitly. + * <p/> + * <strong>Warning:</strong> This operation is intended only for graph system providers and deployment + * operators that must prohibit a strategy from the global registry. The denial cannot be reversed for the + * lifetime of this cache. Use {@link #unregisterStrategy(Class)} when the strategy should only be removed from + * the registry and may be registered again later. + */ + public static void denyStrategy(final Class<? extends TraversalStrategy> clazz) { + unregisterStrategy(clazz); + DENIED_STRATEGIES.add(clazz.getName()); + } + /** * Looks up a strategy by its simple name. */ diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/TraversalStrategiesTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/TraversalStrategiesTest.java index 4c72ff7422..91b6b0286d 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/TraversalStrategiesTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/TraversalStrategiesTest.java @@ -53,6 +53,7 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.denyStrategy; import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.getRegisteredStrategyClass; import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.registerStrategy; import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.unregisterStrategy; @@ -223,6 +224,54 @@ public class TraversalStrategiesTest { } } + @Test + public void shouldIgnoreRegistrationOfDeniedStrategy() { + registerStrategy(DeniedStrategy.class); + assertEquals(DeniedStrategy.class, + getRegisteredStrategyClass(DeniedStrategy.class.getSimpleName()).get()); + + denyStrategy(DeniedStrategy.class); + assertFalse(getRegisteredStrategyClass(DeniedStrategy.class.getSimpleName()).isPresent()); + + registerStrategy(DeniedStrategy.class); + assertFalse(getRegisteredStrategyClass(DeniedStrategy.class.getSimpleName()).isPresent()); + } + + @Test + public void shouldNotRemoveDeniedStrategyFromCurrentOrFutureGraphCaches() { + final TraversalStrategies graphStrategies = TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone(). + addStrategies(new DeniedGraphStrategy()); + final TraversalStrategies graphComputerStrategies = + TraversalStrategies.GlobalCache.getStrategies(GraphComputer.class).clone(). + addStrategies(new DeniedGraphStrategy()); + + TraversalStrategies.GlobalCache.registerStrategies(DeniedTestGraph.class, graphStrategies); + TraversalStrategies.GlobalCache.registerStrategies(DeniedTestGraphComputer.class, graphComputerStrategies); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraph.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraphComputer.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + + denyStrategy(DeniedGraphStrategy.class); + + assertFalse(getRegisteredStrategyClass(DeniedGraphStrategy.class.getSimpleName()).isPresent()); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraph.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraphComputer.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + + TraversalStrategies.GlobalCache.registerStrategies(DeniedTestGraph.class, + graphStrategies.clone().addStrategies(new DeniedGraphStrategy())); + TraversalStrategies.GlobalCache.registerStrategies(DeniedTestGraphComputer.class, + graphComputerStrategies.clone().addStrategies(new DeniedGraphStrategy())); + + assertFalse(getRegisteredStrategyClass(DeniedGraphStrategy.class.getSimpleName()).isPresent()); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraph.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + assertTrue(TraversalStrategies.GlobalCache.getStrategies(DeniedTestGraphComputer.class). + getStrategy(DeniedGraphStrategy.class).isPresent()); + } + public static class TestGraphComputer implements GraphComputer { @Override @@ -319,6 +368,12 @@ public class TraversalStrategiesTest { } } + public static class DeniedTestGraph extends TestGraph { + } + + public static class DeniedTestGraphComputer extends TestGraphComputer { + } + /** * Tests that {@link org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies#sortStrategies(java.util.Set)} * works as advertised. This class defines a bunch of dummy strategies which define an order. It is verified @@ -525,6 +580,14 @@ public class TraversalStrategiesTest { } + private static class DeniedStrategy extends DummyStrategy { + + } + + private static class DeniedGraphStrategy extends DummyStrategy { + + } + private static class DummyStrategy<S extends TraversalStrategy> extends AbstractTraversalStrategy<S> { @Override
