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) {
+ }
+ }
+}