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


The following commit(s) were added to refs/heads/3.7-dev by this push:
     new e340913794 Require registered strategies in JavaTranslator CTR
e340913794 is described below

commit e34091379486fe86fa009438bd8d87bbedc249f9
Author: Ken Hu <[email protected]>
AuthorDate: Mon Aug 31 11:24:47 2026 -0700

    Require registered strategies in JavaTranslator CTR
    
    Assisted-by: Codex:gpt-5.6-sol
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/reference/the-traversal.asciidoc          |  3 +
 docs/src/upgrade/release-3.7.x.asciidoc            | 33 +++++---
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   | 13 +++
 .../gremlin/jsr223/JavaTranslatorTest.java         | 99 ++++++++++++++++++++++
 5 files changed, 138 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 70b64a4595..7194e57bc7 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -53,6 +53,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * 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.
+* Restricted `JavaTranslator` reconstruction of `TraversalStrategy` proxies to 
registered strategies that have not been denied with `denyStrategy()`.
 * Restricted GraphBinary `TraversalStrategy` deserialization to registered 
strategies that have not been denied with `denyStrategy()`.
 * Restricted GraphBinary, GraphSON and Gryo `Class` deserialization to classes 
registered with `ClassRegistry.register()`, `registerStrategy()` or 
`registerStrategies()`.
 * Fixed `gremlin-python` GraphBinary serialization of 
`BigInteger`/`BigDecimal` negative boundary values (e.g. `-129`) that raised 
`OverflowError`.
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index 18c53baa3e..2749f76483 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -5747,6 +5747,9 @@ 
TraversalStrategies.GlobalCache.registerStrategies(MyGraph.class, traversalStrat
 TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class);
 ----
 
+Serializers and `JavaTranslator` use this registry when resolving a 
`TraversalStrategyProxy`. TinkerPop's built-in
+strategies are registered by default, but custom strategies must also be 
registered before they are used.
+
 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.
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc 
b/docs/src/upgrade/release-3.7.x.asciidoc
index a97738f3db..380a70cddd 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -255,6 +255,25 @@ 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.
 
+==== Traversal Strategy Registration
+
+Starting with 3.7.7, GraphBinary only deserializes custom `TraversalStrategy` 
classes registered with
+`TraversalStrategies.GlobalCache`. `JavaTranslator` applies the same 
requirement when it reconstructs a
+`TraversalStrategyProxy`. Applications and providers that send custom 
strategies over GraphBinary or translate
+bytecode containing custom strategies must register each strategy before 
requests are processed:
+
+[source,java]
+----
+TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class);
+JavaTranslator.of(g).translate(bytecode);
+----
+
+The presence of a strategy on the application class path is no longer 
sufficient. TinkerPop's built-in strategies are
+registered by default. An unregistered custom strategy produces an error such 
as
+`TraversalStrategy not recognized - com.example.MyStrategy`.
+
+See: 
link:https://tinkerpop.apache.org/docs/3.7.7/reference/#traversalstrategy-registration[TraversalStrategy
 Registration]
+
 === Upgrading for Providers
 
 ==== Graph System Providers
@@ -264,6 +283,8 @@ strings will fail to deserialize after upgrading and will 
need to be migrated to
 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.
+GraphBinary uses this registry when deserializing traversal strategies, so 
providers must register every custom
+strategy sent over GraphBinary.
 
 [source,java]
 ----
@@ -281,17 +302,7 @@ intended only for graph system providers and operators. 
Reversible removal shoul
 TraversalStrategies.GlobalCache.denyStrategy(MyStrategy.class);
 ----
 
-See: 
link:https://tinkerpop.apache.org/docs/3.7.7/reference/#traversalstrategy[TraversalStrategy
 Reference]
-
-===== GraphBinary Strategy Deserialization
-
-Before 3.7.7, GraphBinary deserialization loaded a custom `TraversalStrategy` 
from the application class path based on
-its serialized class name. Starting with 3.7.7, GraphBinary only deserializes 
strategy classes registered with
-`TraversalStrategies.GlobalCache` and rejects denied strategies.
-
-Providers that send custom strategies over GraphBinary must register every 
strategy before requests are deserialized
-using `registerStrategies()` or `registerStrategy()`, as described above. The 
presence of a strategy on the application
-class path is no longer sufficient.
+See: 
link:https://tinkerpop.apache.org/docs/3.7.7/reference/#traversalstrategy-registration[TraversalStrategy
 Registration]
 
 ===== Class Values Must Be Registered
 
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 0a8d3bdc55..caf820c171 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -25,6 +25,8 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
+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.GraphTraversal;
 import 
org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
@@ -181,6 +183,17 @@ public final class JavaTranslator<S extends 
TraversalSource, T extends Traversal
 
     private Object invokeStrategyCreationMethod(final Object delegate, final 
Map<String, Object> map) {
         final Class<?> strategyClass = ((TraversalStrategyProxy) 
delegate).getStrategyClass();
+        if (null == strategyClass || 
!TraversalStrategy.class.isAssignableFrom(strategyClass))
+            throw new IllegalStateException("Class is not a TraversalStrategy 
- " +
+                    (null == strategyClass ? "null" : 
strategyClass.getName()));
+
+        final Class<? extends TraversalStrategy> registeredStrategy =
+                
TraversalStrategies.GlobalCache.getRegisteredStrategyClassByFullName(strategyClass.getName()).
+                        orElseThrow(() -> new IllegalStateException(
+                                "TraversalStrategy not recognized - " + 
strategyClass.getName()));
+        if (registeredStrategy != strategyClass)
+            throw new IllegalStateException("TraversalStrategy not recognized 
- " + strategyClass.getName());
+
         final Map<String, Method> methodCache = 
localMethodCache.computeIfAbsent(strategyClass, k -> {
             final Map<String, Method> cacheEntry = new HashMap<>();
             try {
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslatorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslatorTest.java
new file mode 100644
index 0000000000..5375a0e0c2
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import org.apache.commons.configuration2.BaseConfiguration;
+import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
+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.strategy.AbstractTraversalStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
+import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class JavaTranslatorTest {
+
+    private final GraphTraversalSource g = EmptyGraph.instance().traversal();
+    private final JavaTranslator<GraphTraversalSource, Traversal.Admin<?, ?>> 
translator = JavaTranslator.of(g);
+
+    @Test
+    public void shouldTranslateRegisteredTraversalStrategyProxy() {
+        final Bytecode bytecode = new Bytecode();
+        bytecode.addSource(TraversalSource.Symbols.withStrategies,
+                new TraversalStrategyProxy<>(ReadOnlyStrategy.instance()));
+        bytecode.addStep("V");
+
+        
assertEquals(g.withStrategies(ReadOnlyStrategy.instance()).V().asAdmin(), 
translator.translate(bytecode));
+    }
+
+    @Test
+    public void 
shouldRejectUnregisteredTraversalStrategyProxyBeforeInvokingFactory() {
+        final Bytecode bytecode = new Bytecode();
+        bytecode.addSource(TraversalSource.Symbols.withStrategies,
+                new TraversalStrategyProxy<>(UnregisteredStrategy.class, new 
BaseConfiguration()));
+        bytecode.addStep("V");
+
+        final IllegalStateException exception =
+                assertThrows(IllegalStateException.class, () -> 
translator.translate(bytecode));
+        assertThat(exception.getMessage(), containsString(
+                "TraversalStrategy not recognized - " + 
UnregisteredStrategy.class.getName()));
+        assertThat(UnregisteredStrategy.instanceInvoked, is(false));
+    }
+
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void shouldRejectTraversalStrategyProxyForNonStrategyClass() {
+        final Bytecode bytecode = new Bytecode();
+        bytecode.addSource(TraversalSource.Symbols.withStrategies,
+                new TraversalStrategyProxy(String.class, new 
BaseConfiguration()));
+        bytecode.addStep("V");
+
+        final IllegalStateException exception =
+                assertThrows(IllegalStateException.class, () -> 
translator.translate(bytecode));
+        assertThat(exception.getMessage(), containsString(
+                "Class is not a TraversalStrategy - " + 
String.class.getName()));
+    }
+
+    private static final class UnregisteredStrategy
+            extends 
AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy>
+            implements TraversalStrategy.DecorationStrategy {
+
+        private static final UnregisteredStrategy INSTANCE = new 
UnregisteredStrategy();
+        private static boolean instanceInvoked = false;
+
+        public static UnregisteredStrategy instance() {
+            instanceInvoked = true;
+            return INSTANCE;
+        }
+
+        @Override
+        public void apply(final Traversal.Admin<?, ?> traversal) {
+        }
+    }
+}

Reply via email to