This is an automated email from the ASF dual-hosted git repository.
kenhuuu pushed a commit to branch 3.8-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.8-dev by this push:
new 696d8636c4 Restrict GraphSON2/3 traversal strategy deserialization
696d8636c4 is described below
commit 696d8636c4c455ac423c56aac3a64fc2b3f3be4f
Author: Ken Hu <[email protected]>
AuthorDate: Thu Aug 20 19:32:22 2026 -0700
Restrict GraphSON2/3 traversal strategy deserialization
Assisted-by: Codex:gpt-5.6-sol
---
CHANGELOG.asciidoc | 1 +
docs/src/reference/gremlin-applications.asciidoc | 16 ++-
docs/src/upgrade/release-3.8.2.asciidoc | 21 ++++
.../io/graphson/TraversalSerializersV2.java | 9 +-
.../io/graphson/TraversalSerializersV3.java | 9 +-
.../graphson/GraphSONMapperEmbeddedTypeTest.java | 35 ++++--
...versalStrategyProxyJacksonDeserializerTest.java | 127 +++++++++++++++++++++
.../util/ser/GraphSONMessageSerializerV2Test.java | 40 +++++++
.../util/ser/GraphSONMessageSerializerV3Test.java | 39 +++++++
9 files changed, 274 insertions(+), 23 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 9770ca3c2d..44f00f5b97 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -36,6 +36,7 @@ This release also includes changes from prior 3.7.x releases.
* Added a `propertyMap()` helper to view an element's properties as a map
keyed by property key, on the `Element` structure API in `gremlin-core`
(inherited by `Vertex`, `Edge`, and `VertexProperty`) and on `Vertex`, `Edge`,
and `VertexProperty` in `gremlin-javascript`, `gremlin-python`,
`gremlin-dotnet`, and `gremlin-go`.
* Fixed a bug in `gremlin-go` where a `VertexProperty` deserialized from
GraphBinary did not have its `Key` field populated, causing
`Vertex.PropertyMap()` to group properties under an empty key.
* Removed `final` from `RepeatStep` to allow providers to extend it.
+* Restricted GraphSON 2.0 and 3.0 `TraversalStrategy` deserialization to
registered strategies.
[[release-3-8-1]]
=== TinkerPop 3.8.1 (Release Date: April 1, 2026)
diff --git a/docs/src/reference/gremlin-applications.asciidoc
b/docs/src/reference/gremlin-applications.asciidoc
index 36a9b0a048..dbf46214e0 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -1218,6 +1218,21 @@ Gremlin Server is configured by default with GraphSON
3.0 as shown above. It has
|ioRegistries |A list of `IoRegistry` implementations to be applied to the
serializer. |_none_
|=========================================================
+The GraphSON 2.0 and 3.0 reference implementations only deserialize a
`TraversalStrategy` when its class is registered
+with `TraversalStrategies.GlobalCache`. TinkerPop's built-in strategies are
registered by default. Providers must
+register every custom strategy before GraphSON input is read, either as part
of a graph or graph computer strategy set
+with `registerStrategies()`, or individually with `registerStrategy()`:
+
+[source,java]
+----
+TraversalStrategies.GlobalCache.registerStrategies(MyGraph.class,
traversalStrategies);
+// or
+TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class);
+----
+
+The presence of a strategy on the application class path is not sufficient.
Registration permits serialized data to
+construct the strategy from its configuration, so all strategies should be
registered.
+
It is worth noting that GraphSON 1.0 still has some appeal for some users as
it can be configured to produce an untyped
JSON format which is a bit easier to consume than its successors which embed
data types into the output. This version
of GraphSON tends to be the one that users like to utilize when
<<connecting-via-http,connecting via HTTP>> and is still
@@ -3196,4 +3211,3 @@ create large or frequently changing value sets, improving
performance and stabil
Consult the MCP client documentation for how environment variables are
supplied and how tool calls are approved and
presented to the user.
-
diff --git a/docs/src/upgrade/release-3.8.2.asciidoc
b/docs/src/upgrade/release-3.8.2.asciidoc
index 2b8ffbd336..61330de328 100644
--- a/docs/src/upgrade/release-3.8.2.asciidoc
+++ b/docs/src/upgrade/release-3.8.2.asciidoc
@@ -77,3 +77,24 @@ Map<String, List<Property<Object>>> propertyMap =
vertex.propertyMap();
----
See: link:https://issues.apache.org/jira/browse/TINKERPOP-3187[TINKERPOP-3187]
+
+=== Upgrading for Providers
+
+==== Graph System Providers
+
+===== GraphSON Strategy Registry
+
+Before 3.8.2, GraphSON 2.0 and 3.0 deserialization loaded a custom
`TraversalStrategy` from the application class path
+based on its serialized class name. Starting with 3.8.2, GraphSON only
deserializes strategies registered with
+`TraversalStrategies.GlobalCache`.
+
+Providers that send custom strategies over GraphSON must register every
strategy before input is deserialized. Either
+`registerStrategies()` or `registerStrategy()` can be used:
+
+[source,java]
+----
+TraversalStrategies.GlobalCache.registerStrategies(MyGraph.class,
traversalStrategies);
+// or
+TraversalStrategies.GlobalCache.registerStrategy(MyStrategy.class);
+----
+
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java
index 218b6fd94f..7035ec8105 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java
@@ -28,6 +28,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.NotP;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
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.Traverser;
import
org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
@@ -544,11 +545,9 @@ final class TraversalSerializersV2 {
final Class clasz;
final Map<String,Object> mapConf = (Map<String,Object>)
data.get("conf");
if (null == this.clazz || this.clazz ==
TraversalStrategyProxy.class) {
- try {
- clasz = Class.forName(data.get("fqcn").toString());
- } catch (Exception ex) {
- throw new IllegalArgumentException("Could not load class "
+ mapConf.get("fqcn").toString(), ex);
- }
+ final String className = data.get("fqcn").toString();
+ clasz =
TraversalStrategies.GlobalCache.getRegisteredStrategyClassByFullName(className).
+ orElseThrow(() -> new
IllegalArgumentException("TraversalStrategy not recognized - " + className));
} else {
clasz = this.clazz;
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java
index ab5d47a230..ec98331826 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java
@@ -28,6 +28,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.NotP;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
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.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
@@ -637,11 +638,9 @@ final class TraversalSerializersV3 {
final Class clasz;
final Map<String,Object> mapConf = (Map<String,Object>)
data.get("conf");
if (null == this.clazz || this.clazz ==
TraversalStrategyProxy.class) {
- try {
- clasz = Class.forName(data.get("fqcn").toString());
- } catch (Exception ex) {
- throw new IllegalArgumentException("Could not load class "
+ mapConf.get("fqcn").toString(), ex);
- }
+ final String className = data.get("fqcn").toString();
+ clasz =
TraversalStrategies.GlobalCache.getRegisteredStrategyClassByFullName(className).
+ orElseThrow(() -> new
IllegalArgumentException("TraversalStrategy not recognized - " + className));
} else {
clasz = this.clazz;
}
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
index 510df3d613..6bf345996a 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Compare;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
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.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchStep;
@@ -512,24 +513,34 @@ public class GraphSONMapperEmbeddedTypeTest extends
AbstractGraphSONTest {
public void shouldHandleTraversalStrategyProxy() throws Exception {
assumeThat(version, either(startsWith("v2")).or(startsWith("v3")));
- final TraversalStrategyProxy o = new
TraversalStrategyProxy(DummyTraversalStrategy.instance());
- final TraversalStrategyProxy strategyProxy =
serializeDeserialize(mapper, o, TraversalStrategyProxy.class);
- assertThat(strategyProxy, instanceOf(TraversalStrategyProxy.class));
- assertEquals(DummyTraversalStrategy.class,
strategyProxy.getStrategyClass());
-
DummyTraversalStrategy.instance().getConfiguration().getKeys().forEachRemaining(
- k ->
assertEquals(DummyTraversalStrategy.instance().getConfiguration().getProperty(k),
strategyProxy.getConfiguration().getProperty(k)));
+
TraversalStrategies.GlobalCache.registerStrategy(DummyTraversalStrategy.class);
+ try {
+ final TraversalStrategyProxy o = new
TraversalStrategyProxy(DummyTraversalStrategy.instance());
+ final TraversalStrategyProxy strategyProxy =
serializeDeserialize(mapper, o, TraversalStrategyProxy.class);
+ assertThat(strategyProxy,
instanceOf(TraversalStrategyProxy.class));
+ assertEquals(DummyTraversalStrategy.class,
strategyProxy.getStrategyClass());
+
DummyTraversalStrategy.instance().getConfiguration().getKeys().forEachRemaining(
+ k ->
assertEquals(DummyTraversalStrategy.instance().getConfiguration().getProperty(k),
strategyProxy.getConfiguration().getProperty(k)));
+ } finally {
+
TraversalStrategies.GlobalCache.unregisterStrategy(DummyTraversalStrategy.class);
+ }
}
@Test
public void shouldHandleTraversalStrategyProxyWithConfig() throws
Exception {
assumeThat(version, either(startsWith("v2")).or(startsWith("v3")));
- final TraversalStrategyProxy o = new
TraversalStrategyProxy(DummyConfiguredTraversalStrategy.instance());
- final TraversalStrategyProxy strategyProxy =
serializeDeserialize(mapper, o, TraversalStrategyProxy.class);
- assertThat(strategyProxy, instanceOf(TraversalStrategyProxy.class));
- assertEquals(DummyConfiguredTraversalStrategy.class,
strategyProxy.getStrategyClass());
-
DummyConfiguredTraversalStrategy.instance().getConfiguration().getKeys().forEachRemaining(
- k ->
assertEquals(DummyConfiguredTraversalStrategy.instance().getConfiguration().getProperty(k),
strategyProxy.getConfiguration().getProperty(k)));
+
TraversalStrategies.GlobalCache.registerStrategy(DummyConfiguredTraversalStrategy.class);
+ try {
+ final TraversalStrategyProxy o = new
TraversalStrategyProxy(DummyConfiguredTraversalStrategy.instance());
+ final TraversalStrategyProxy strategyProxy =
serializeDeserialize(mapper, o, TraversalStrategyProxy.class);
+ assertThat(strategyProxy,
instanceOf(TraversalStrategyProxy.class));
+ assertEquals(DummyConfiguredTraversalStrategy.class,
strategyProxy.getStrategyClass());
+
DummyConfiguredTraversalStrategy.instance().getConfiguration().getKeys().forEachRemaining(
+ k ->
assertEquals(DummyConfiguredTraversalStrategy.instance().getConfiguration().getProperty(k),
strategyProxy.getConfiguration().getProperty(k)));
+ } finally {
+
TraversalStrategies.GlobalCache.unregisterStrategy(DummyConfiguredTraversalStrategy.class);
+ }
}
@Test
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalStrategyProxyJacksonDeserializerTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalStrategyProxyJacksonDeserializerTest.java
new file mode 100644
index 0000000000..bfef5f78cb
--- /dev/null
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalStrategyProxyJacksonDeserializerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.structure.io.graphson;
+
+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.TraversalStrategyProxy;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
+import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+@RunWith(Parameterized.class)
+public class TraversalStrategyProxyJacksonDeserializerTest {
+
+ private static boolean loadRecordingStrategyInitialized;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {"v2", GraphSONMapper.build().version(GraphSONVersion.V2_0).
+
typeInfo(TypeInfo.PARTIAL_TYPES).create().createMapper()},
+ {"v3", GraphSONMapper.build().version(GraphSONVersion.V3_0).
+
typeInfo(TypeInfo.PARTIAL_TYPES).create().createMapper()}
+ });
+ }
+
+ @Parameterized.Parameter(0)
+ public String version;
+
+ @Parameterized.Parameter(1)
+ public ObjectMapper mapper;
+
+ @After
+ public void unregisterStrategy() {
+
TraversalStrategies.GlobalCache.unregisterStrategy(LoadRecordingStrategy.class);
+ }
+
+ @Test
+ public void shouldRejectStrategyThatIsNotRegistered() throws Exception {
+ final String fqcn = LoadRecordingStrategy.class.getName();
+ try {
+ readStrategy(fqcn);
+ fail("A strategy that is not registered must not deserialize");
+ } catch (IOException ex) {
+ assertThat(ex.getMessage(), containsString("TraversalStrategy not
recognized - " + fqcn));
+ }
+ }
+
+ @Test
+ public void shouldRejectStrategyThatIsNotRegisteredWithoutInitializingIt()
throws Exception {
+ final String fqcn = LoadRecordingStrategy.class.getName();
+ try {
+ readStrategy(fqcn);
+ fail("A strategy that is not registered must not deserialize");
+ } catch (IOException ignored) {
+ // asserted on by shouldRejectStrategyThatIsNotRegistered
+ }
+
+ assertFalse("The rejected strategy was initialized, so the check ran
after the class was loaded",
+ loadRecordingStrategyInitialized);
+ }
+
+ @Test
+ public void shouldAdmitStrategyRegisteredAsABuiltIn() throws Exception {
+ assertEquals(SubgraphStrategy.class,
+
readStrategy(SubgraphStrategy.class.getName()).getStrategyClass());
+ }
+
+ @Test
+ public void shouldAdmitStrategyRegisteredByAProvider() throws Exception {
+
TraversalStrategies.GlobalCache.registerStrategy(LoadRecordingStrategy.class);
+
+ assertEquals(LoadRecordingStrategy.class,
+
readStrategy(LoadRecordingStrategy.class.getName()).getStrategyClass());
+ }
+
+ private TraversalStrategyProxy readStrategy(final String fqcn) throws
IOException {
+ return mapper.readValue(
+
String.format("{\"@type\":\"g:TraversalStrategy\",\"@value\":{\"fqcn\":\"%s\",\"conf\":{}}}",
+ fqcn),
+ TraversalStrategyProxy.class);
+ }
+
+ private static final class LoadRecordingStrategy
+ extends
AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy>
+ implements TraversalStrategy.DecorationStrategy {
+
+ static {
+ loadRecordingStrategyInitialized = true;
+ }
+
+ @Override
+ public void apply(final Traversal.Admin<?, ?> traversal) {
+ // do nothing
+ }
+ }
+}
diff --git
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV2Test.java
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV2Test.java
index bb5f77bd18..49c20b43a9 100644
---
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV2Test.java
+++
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV2Test.java
@@ -21,12 +21,17 @@ package org.apache.tinkerpop.gremlin.util.ser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
+import org.apache.commons.configuration2.BaseConfiguration;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.util.MessageSerializer;
import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
import
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Property;
@@ -70,7 +75,9 @@ import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -85,6 +92,7 @@ import static org.junit.Assert.fail;
public class GraphSONMessageSerializerV2Test {
public static final GraphSONMessageSerializerV2 SERIALIZER = new
GraphSONMessageSerializerV2();
+ private static boolean unregisteredStrategyInitialized;
private static final RequestMessage msg = RequestMessage.build("op")
.overrideRequestId(UUID.fromString("2D62161B-9544-4F39-AF44-62EC49F9A595")).create();
private static final ObjectMapper mapper = new ObjectMapper();
@@ -423,6 +431,24 @@ public class GraphSONMessageSerializerV2Test {
assertEquals(ResponseStatusCode.SUCCESS.getValue(),
deserialized.getStatus().getCode().getValue());
assertEquals("worked", deserialized.getStatus().getMessage());
}
+
+ @Test
+ public void shouldRejectUnregisteredTraversalStrategyInResponse() throws
Exception {
+ final String fqcn = UnregisteredStrategy.class.getName();
+ final TraversalStrategyProxy<UnregisteredStrategy> strategy =
+ new TraversalStrategyProxy<>(UnregisteredStrategy.class, new
BaseConfiguration());
+ final String response = SERIALIZER.serializeResponseAsString(
+
ResponseMessage.build(UUID.randomUUID()).result(strategy).create(), allocator);
+
+ try {
+ SERIALIZER.deserializeResponse(response);
+ fail("An unregistered strategy in a server response must not
deserialize");
+ } catch (SerializationException ex) {
+ assertThat(ex.getMessage(), containsString("TraversalStrategy not
recognized - " + fqcn));
+ }
+
+ assertFalse("The rejected strategy was initialized",
unregisteredStrategyInitialized);
+ }
@Test
public void shouldSerializeToTreeJson() throws Exception {
@@ -589,6 +615,20 @@ public class GraphSONMessageSerializerV2Test {
}
}
+ private static final class UnregisteredStrategy
+ extends
AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy>
+ implements TraversalStrategy.DecorationStrategy {
+
+ static {
+ unregisteredStrategyInitialized = true;
+ }
+
+ @Override
+ public void apply(final Traversal.Admin<?, ?> traversal) {
+ // do nothing
+ }
+ }
+
public static class ColorIoRegistry extends AbstractIoRegistry {
public ColorIoRegistry() {
register(GraphSONIo.class, null, new ColorSimpleModule());
diff --git
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
index 3a9c0e537d..440343ea84 100644
---
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
+++
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
@@ -21,7 +21,11 @@ package org.apache.tinkerpop.gremlin.util.ser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
+import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+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.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
import org.apache.tinkerpop.gremlin.util.MessageSerializer;
import org.apache.tinkerpop.gremlin.util.Tokens;
@@ -55,7 +59,9 @@ import java.util.UUID;
import static
org.apache.tinkerpop.gremlin.util.MockitoHamcrestMatcherAdapter.reflectionEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItemInArray;
+import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -69,6 +75,7 @@ import static org.junit.Assert.fail;
@SuppressWarnings("unchecked")
public class GraphSONMessageSerializerV3Test {
+ private static boolean unregisteredStrategyInitialized;
private final UUID requestId =
UUID.fromString("6457272A-4018-4538-B9AE-08DD5DDC0AA1");
private final ResponseMessage.Builder responseMessageBuilder =
ResponseMessage.build(requestId);
private final static ByteBufAllocator allocator =
UnpooledByteBufAllocator.DEFAULT;
@@ -342,6 +349,24 @@ public class GraphSONMessageSerializerV3Test {
assertEquals("worked", deserialized.getStatus().getMessage());
}
+ @Test
+ public void shouldRejectUnregisteredTraversalStrategyInResponse() throws
Exception {
+ final String fqcn = UnregisteredStrategy.class.getName();
+ final TraversalStrategyProxy<UnregisteredStrategy> strategy =
+ new TraversalStrategyProxy<>(UnregisteredStrategy.class, new
BaseConfiguration());
+ final String response = serializer.serializeResponseAsString(
+
ResponseMessage.build(UUID.randomUUID()).result(strategy).create(), allocator);
+
+ try {
+ serializer.deserializeResponse(response);
+ fail("An unregistered strategy in a server response must not
deserialize");
+ } catch (SerializationException ex) {
+ assertThat(ex.getMessage(), containsString("TraversalStrategy not
recognized - " + fqcn));
+ }
+
+ assertFalse("The rejected strategy was initialized",
unregisteredStrategyInitialized);
+ }
+
@Test
public void shouldDeserializeNotPredicate() throws Exception {
final String requestMessageWithP =
"{\"requestId\":{\"@type\":\"g:UUID\",\"@value\":\"0397b9c0-ffab-470e-a6a8-644fc80c01d6\"},\"op\":\"bytecode\",\"processor\":\"traversal\",\"args\":{\"gremlin\":{\"@type\":\"g:Bytecode\",\"@value\":{\"step\":[[\"V\"],[\"hasLabel\",\"person\"],[\"has\",\"age\",{\"@type\":\"g:P\",\"@value\":{\"predicate\":\"not\",\"value\":{\"@type\":\"g:P\",\"@value\":{\"predicate\":\"lte\",\"value\":{\"@type\":\"g:Int32\",\"@value\":10}}}}}]]}},\"aliases\":{\"g\
[...]
@@ -416,4 +441,18 @@ public class GraphSONMessageSerializerV3Test {
private ResponseMessage convert(final Object toSerialize) throws
SerializationException {
return convert(toSerialize, this.serializer);
}
+
+ private static final class UnregisteredStrategy
+ extends
AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy>
+ implements TraversalStrategy.DecorationStrategy {
+
+ static {
+ unregisteredStrategyInitialized = true;
+ }
+
+ @Override
+ public void apply(final Traversal.Admin<?, ?> traversal) {
+ // do nothing
+ }
+ }
}