This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch bp-str-reg in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 07fe1acfd7315b54eafd6df94a2924ba56555d4d 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. RemoteStrategy, RequirementsStrategy, SackStrategy and SideEffectStrategy are concrete gremlin-core strategies that were omitted from the 3.8 seed list, so register them with the other built-ins. SparqlStrategy belongs to the optional sparql-gremlin module, so it registers itself when loaded instead of adding a core dependency. Adds registry contract coverage for built-ins, provider strategies, lifecycle, duplicate simple names and invalid lookups. 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 | 99 ++++++++++++++++++- .../gremlin/process/TraversalStrategiesTest.java | 109 +++++++++++++++++++++ .../process/traversal/strategy/SparqlStrategy.java | 6 ++ .../dsl/sparql/SparqlTraversalSourceTest.java | 8 ++ 4 files changed, 219 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..bb07187b14 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,27 @@ 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.remote.traversal.strategy.decoration.RemoteStrategy; +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.RequirementsStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SackStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SideEffectStrategy; +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 +53,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 +235,42 @@ 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(RemoteStrategy.class.getSimpleName(), RemoteStrategy.class); + put(RequirementsStrategy.class.getSimpleName(), RequirementsStrategy.class); + put(SackStrategy.class.getSimpleName(), SackStrategy.class); + put(SeedStrategy.class.getSimpleName(), SeedStrategy.class); + put(SideEffectStrategy.class.getSimpleName(), SideEffectStrategy.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 +289,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 +302,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 +317,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..e7a5b88b84 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 @@ -24,10 +24,16 @@ import org.apache.tinkerpop.gremlin.process.computer.ComputerResult; import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; import org.apache.tinkerpop.gremlin.process.computer.MapReduce; import org.apache.tinkerpop.gremlin.process.computer.VertexProgram; +import org.apache.tinkerpop.gremlin.process.remote.traversal.strategy.decoration.RemoteStrategy; 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.RequirementsStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SackStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SideEffectStrategy; +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 +55,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 +145,89 @@ public class TraversalStrategiesTest { assertTrue(strategies.getStrategy(StrategyE.class).isPresent()); } + @Test + public void shouldRegisterBuiltInStrategiesByName() { + assertEquals(ReadOnlyStrategy.class, getRegisteredStrategyClass(ReadOnlyStrategy.class.getSimpleName()).get()); + assertEquals(CountStrategy.class, getRegisteredStrategyClass(CountStrategy.class.getSimpleName()).get()); + assertEquals(RemoteStrategy.class, getRegisteredStrategyClass(RemoteStrategy.class.getSimpleName()).get()); + assertEquals(RequirementsStrategy.class, + getRegisteredStrategyClass(RequirementsStrategy.class.getSimpleName()).get()); + assertEquals(SackStrategy.class, getRegisteredStrategyClass(SackStrategy.class.getSimpleName()).get()); + assertEquals(SideEffectStrategy.class, + getRegisteredStrategyClass(SideEffectStrategy.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 +512,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> { diff --git a/sparql-gremlin/src/main/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/strategy/SparqlStrategy.java b/sparql-gremlin/src/main/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/strategy/SparqlStrategy.java index 7b6dd7cdc4..32d22cf4ab 100644 --- a/sparql-gremlin/src/main/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/strategy/SparqlStrategy.java +++ b/sparql-gremlin/src/main/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/strategy/SparqlStrategy.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.sparql.process.traversal.strategy; import org.apache.tinkerpop.gremlin.process.remote.traversal.strategy.decoration.RemoteStrategy; 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.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectStep; @@ -52,6 +53,11 @@ public class SparqlStrategy extends AbstractTraversalStrategy<TraversalStrategy. private static final Set<Class<? extends DecorationStrategy>> PRIORS = Collections.singleton(RemoteStrategy.class); + static { + // makes the strategy resolvable by name, which serializers require of a strategy that arrives in a request + TraversalStrategies.GlobalCache.registerStrategy(SparqlStrategy.class); + } + private SparqlStrategy() {} public static SparqlStrategy instance() { diff --git a/sparql-gremlin/src/test/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/dsl/sparql/SparqlTraversalSourceTest.java b/sparql-gremlin/src/test/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/dsl/sparql/SparqlTraversalSourceTest.java index 552ac73e49..b9139ad39d 100644 --- a/sparql-gremlin/src/test/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/dsl/sparql/SparqlTraversalSourceTest.java +++ b/sparql-gremlin/src/test/java/org/apache/tinkerpop/gremlin/sparql/process/traversal/dsl/sparql/SparqlTraversalSourceTest.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.sparql.process.traversal.dsl.sparql; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.sparql.process.traversal.strategy.SparqlStrategy; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory; @@ -31,6 +32,7 @@ import java.util.List; import java.util.Map; import static org.apache.tinkerpop.gremlin.process.traversal.Operator.mult; +import static org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies.GlobalCache.getRegisteredStrategyClass; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; @@ -46,6 +48,12 @@ public class SparqlTraversalSourceTest { private static final SparqlTraversalSource g = graph.traversal(SparqlTraversalSource.class); private static final GraphTraversalSource _g = graph.traversal(); + @Test + public void shouldRegisterSparqlStrategy() { + SparqlStrategy.instance(); + assertEquals(SparqlStrategy.class, getRegisteredStrategyClass(SparqlStrategy.class.getSimpleName()).get()); + } + @Test public void shouldStartWithSparqlReturningMapAndEndWithGremlin() { final List<?> x = g.sparql("SELECT ?name ?age WHERE { ?person v:name ?name . ?person v:age ?age }").select("name").toList();
