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

Reply via email to