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 5dda51e73ea528d2ee3277acfa68ba5f0c404d50 Author: Ken Hu <[email protected]> AuthorDate: Mon Aug 17 14:22:44 2026 -0700 Backport TraversalStrategies.GlobalCache.GLOBAL_REGISTRY to 3.7 Backports the 3.8 GLOBAL_REGISTRY so strategies can be looked up by name. registerStrategies() publishes provider defaults, while registerStrategy() and unregisterStrategy() support strategies that should be named without becoming defaults. Nothing in 3.7 consults the registry yet. It is the prerequisite for resolving a strategy named by a serialized traversal against what trusted code declared, rather than loading the named class. Assisted-by: Claude Code:claude-opus-5 --- .../process/traversal/TraversalStrategies.java | 91 +++++++++++++++++- .../gremlin/process/TraversalStrategiesTest.java | 104 +++++++++++++++++++++ 2 files changed, 192 insertions(+), 3 deletions(-) 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 aea45618ab..d9a9248f46 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 @@ -19,11 +19,23 @@ package org.apache.tinkerpop.gremlin.process.traversal; import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; +import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy; import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.finalization.ComputerFinalizationStrategy; import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization.GraphFilterStrategy; import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization.MessagePassingReductionStrategy; +import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification.VertexProgramRestrictionStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ConnectiveStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.HaltedTraverserStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.ProfileStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.ReferenceElementStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.AdjacentToIncidentStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategy; @@ -37,8 +49,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Matc import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.OrderLimitStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathProcessorStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathRetractionStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ComputerVerificationStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReservedKeysVerificationStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.StandardVerificationStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrategies; import org.apache.tinkerpop.gremlin.structure.Graph; @@ -214,6 +231,38 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T private static final Map<Class<? extends Graph>, TraversalStrategies> GRAPH_CACHE = new HashMap<>(); private static final Map<Class<? extends GraphComputer>, TraversalStrategies> GRAPH_COMPUTER_CACHE = new HashMap<>(); + /** + * A register of the simple names for all strategies. + */ + private static final Map<String, Class<? extends TraversalStrategy>> GLOBAL_REGISTRY = new HashMap<String, Class<? extends TraversalStrategy>>() {{ + // decorations + put(ConnectiveStrategy.class.getSimpleName(), ConnectiveStrategy.class); + put(ElementIdStrategy.class.getSimpleName(), ElementIdStrategy.class); + put(EventStrategy.class.getSimpleName(), EventStrategy.class); + put(HaltedTraverserStrategy.class.getSimpleName(), HaltedTraverserStrategy.class); + put(OptionsStrategy.class.getSimpleName(), OptionsStrategy.class); + put(PartitionStrategy.class.getSimpleName(), PartitionStrategy.class); + put(SeedStrategy.class.getSimpleName(), SeedStrategy.class); + put(SubgraphStrategy.class.getSimpleName(), SubgraphStrategy.class); + put(VertexProgramStrategy.class.getSimpleName(), VertexProgramStrategy.class); + + // finalization + put(MatchAlgorithmStrategy.class.getSimpleName(), MatchAlgorithmStrategy.class); + put(ReferenceElementStrategy.class.getSimpleName(), ReferenceElementStrategy.class); + + // optimizations + put(ProductiveByStrategy.class.getSimpleName(), ProductiveByStrategy.class); + put(PathRetractionStrategy.class.getSimpleName(), PathRetractionStrategy.class); + put(RepeatUnrollStrategy.class.getSimpleName(), RepeatUnrollStrategy.class); + + // verification + put(EdgeLabelVerificationStrategy.class.getSimpleName(), EdgeLabelVerificationStrategy.class); + put(LambdaRestrictionStrategy.class.getSimpleName(), LambdaRestrictionStrategy.class); + put(ReadOnlyStrategy.class.getSimpleName(), ReadOnlyStrategy.class); + put(ReservedKeysVerificationStrategy.class.getSimpleName(), ReservedKeysVerificationStrategy.class); + put(VertexProgramRestrictionStrategy.class.getSimpleName(), VertexProgramRestrictionStrategy.class); + }}; + static { final TraversalStrategies graphStrategies = new DefaultTraversalStrategies(); graphStrategies.addStrategies( @@ -232,8 +281,8 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T LazyBarrierStrategy.instance(), ProfileStrategy.instance(), StandardVerificationStrategy.instance()); - GRAPH_CACHE.put(Graph.class, graphStrategies); - GRAPH_CACHE.put(EmptyGraph.class, new DefaultTraversalStrategies()); + registerStrategies(Graph.class, graphStrategies); + registerStrategies(EmptyGraph.class, new DefaultTraversalStrategies()); ///////////////////// @@ -245,9 +294,14 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T PathProcessorStrategy.instance(), ComputerFinalizationStrategy.instance(), ComputerVerificationStrategy.instance()); - GRAPH_COMPUTER_CACHE.put(GraphComputer.class, graphComputerStrategies); + registerStrategies(GraphComputer.class, graphComputerStrategies); } + /** + * 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. + */ public static void registerStrategies(final Class graphOrGraphComputerClass, final TraversalStrategies traversalStrategies) { if (Graph.class.isAssignableFrom(graphOrGraphComputerClass)) GRAPH_CACHE.put(graphOrGraphComputerClass, traversalStrategies); @@ -255,6 +309,37 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T GRAPH_COMPUTER_CACHE.put(graphOrGraphComputerClass, traversalStrategies); else 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())); + } + + /** + * 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. + */ + public static void registerStrategy(final Class<? extends TraversalStrategy> clazz) { + 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[])}}. + */ + public static void unregisterStrategy(final Class<? extends TraversalStrategy> clazz) { + GLOBAL_REGISTRY.remove(clazz.getSimpleName()); + } + + /** + * Looks up a strategy by its simple name. + */ + public static Optional<? extends Class<? extends TraversalStrategy>> getRegisteredStrategyClass(final String strategyName) { + if (GLOBAL_REGISTRY.containsKey(strategyName)) + return Optional.of(GLOBAL_REGISTRY.get(strategyName)); + + return Optional.empty(); } public static TraversalStrategies getStrategies(final Class graphOrGraphComputerClass) { 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 4d0fd22cbf..4c72ff7422 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 @@ -28,6 +28,10 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Property; @@ -49,6 +53,9 @@ 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.getRegisteredStrategyClass; +import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.registerStrategy; +import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.unregisterStrategy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -136,6 +143,86 @@ public class TraversalStrategiesTest { assertTrue(strategies.getStrategy(StrategyE.class).isPresent()); } + @Test + public void shouldRegisterBuiltInStrategiesByName() { + assertEquals(OptionsStrategy.class, getRegisteredStrategyClass(OptionsStrategy.class.getSimpleName()).get()); + assertEquals(MatchAlgorithmStrategy.class, + getRegisteredStrategyClass(MatchAlgorithmStrategy.class.getSimpleName()).get()); + assertEquals(ReadOnlyStrategy.class, getRegisteredStrategyClass(ReadOnlyStrategy.class.getSimpleName()).get()); + assertEquals(CountStrategy.class, getRegisteredStrategyClass(CountStrategy.class.getSimpleName()).get()); + } + + @Test + public void shouldRegisterGraphAndGraphComputerStrategiesByName() { + assertEquals(StrategyA.class, getRegisteredStrategyClass(StrategyA.class.getSimpleName()).get()); + assertEquals(StrategyB.class, getRegisteredStrategyClass(StrategyB.class.getSimpleName()).get()); + assertEquals(StrategyC.class, getRegisteredStrategyClass(StrategyC.class.getSimpleName()).get()); + } + + @Test + public void shouldRegisterAndUnregisterStrategyByNameWithoutChangingDefaults() { + unregisterStrategy(StrategyD.class); + assertFalse(getRegisteredStrategyClass(StrategyD.class.getSimpleName()).isPresent()); + + try { + registerStrategy(StrategyD.class); + assertEquals(StrategyD.class, getRegisteredStrategyClass(StrategyD.class.getSimpleName()).get()); + + // registerStrategy() only adds the class to GLOBAL_REGISTRY and must not alter graph defaults + assertFalse(TraversalStrategies.GlobalCache.getStrategies(Graph.class). + getStrategy(StrategyD.class).isPresent()); + } finally { + unregisterStrategy(StrategyD.class); + } + + assertFalse(getRegisteredStrategyClass(StrategyD.class.getSimpleName()).isPresent()); + } + + @Test + public void shouldOverwriteStrategyRegisteredWithSameSimpleName() { + final String strategyName = FirstStrategyNamespace.DuplicateStrategy.class.getSimpleName(); + unregisterStrategy(FirstStrategyNamespace.DuplicateStrategy.class); + + try { + registerStrategy(FirstStrategyNamespace.DuplicateStrategy.class); + assertEquals(FirstStrategyNamespace.DuplicateStrategy.class, + getRegisteredStrategyClass(strategyName).get()); + + registerStrategy(SecondStrategyNamespace.DuplicateStrategy.class); + assertEquals(SecondStrategyNamespace.DuplicateStrategy.class, + getRegisteredStrategyClass(strategyName).get()); + } finally { + unregisterStrategy(SecondStrategyNamespace.DuplicateStrategy.class); + } + + assertFalse(getRegisteredStrategyClass(strategyName).isPresent()); + } + + @Test + public void shouldNotResolveInvalidStrategyNames() { + assertFalse(getRegisteredStrategyClass("UnknownStrategy").isPresent()); + assertFalse(getRegisteredStrategyClass(ReadOnlyStrategy.class.getName()).isPresent()); + assertFalse(getRegisteredStrategyClass("readonlystrategy").isPresent()); + assertFalse(getRegisteredStrategyClass("").isPresent()); + assertFalse(getRegisteredStrategyClass(null).isPresent()); + } + + @Test + public void shouldIgnoreUnregisterOfAbsentStrategy() { + unregisterStrategy(StrategyD.class); + unregisterStrategy(AbsentStrategy.class); + + try { + registerStrategy(StrategyD.class); + unregisterStrategy(AbsentStrategy.class); + + assertEquals(StrategyD.class, getRegisteredStrategyClass(StrategyD.class.getSimpleName()).get()); + assertFalse(getRegisteredStrategyClass(AbsentStrategy.class.getSimpleName()).isPresent()); + } finally { + unregisterStrategy(StrategyD.class); + } + } + public static class TestGraphComputer implements GraphComputer { @Override @@ -420,6 +507,23 @@ public class TraversalStrategiesTest { } + private static class FirstStrategyNamespace { + + private static class DuplicateStrategy extends DummyStrategy { + + } + } + + private static class SecondStrategyNamespace { + + private static class DuplicateStrategy extends DummyStrategy { + + } + } + + private static class AbsentStrategy extends DummyStrategy { + + } private static class DummyStrategy<S extends TraversalStrategy> extends AbstractTraversalStrategy<S> {
