This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch gs23-tgt in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 0942114b3c43698606739bfb04f3317a67275cb5 Author: Ken Hu <[email protected]> AuthorDate: Thu Aug 6 09:29:18 2026 -0700 Restrict TraversalStrategy deserialization to allowed classes Add explicit allow-lists for GraphSON 2/3 and GraphBinary, enforce strategy validation in JavaTranslator, and merge provider allow-lists with custom GraphBinary registry builders. This prevents loading and running classes unintentionally. Assisted-by: Codex:gpt-5.5 --- CHANGELOG.asciidoc | 1 + docs/src/reference/gremlin-applications.asciidoc | 19 +- docs/src/upgrade/release-3.8.2.asciidoc | 57 +++++ .../tinkerpop/gremlin/jsr223/JavaTranslator.java | 4 + .../strategy/TraversalStrategyResolver.java | 258 +++++++++++++++++++++ .../io/binary/TypeSerializerRegistry.java | 39 +++- .../binary/types/TraversalStrategySerializer.java | 28 ++- .../structure/io/graphson/GraphSONMapper.java | 17 +- .../structure/io/graphson/GraphSONModule.java | 44 +++- .../io/graphson/TraversalSerializersV2.java | 22 +- .../io/graphson/TraversalSerializersV3.java | 22 +- .../gremlin/jsr223/JavaTranslatorTest.java | 33 ++- .../strategy/TestTraversalStrategies.java | 79 +++++++ .../graphson/GraphSONMapperEmbeddedTypeTest.java | 64 ++--- .../ser/AbstractGraphSONMessageSerializerV2.java | 1 + .../util/ser/AbstractMessageSerializer.java | 41 +++- .../util/ser/GraphBinaryMessageSerializerV1.java | 1 + .../gremlin/util/TestTraversalStrategies.java | 46 ++++ .../util/ser/GraphSONMessageSerializerV3Test.java | 37 ++- .../binary/GraphBinaryMessageSerializerV1Test.java | 51 +++- .../types/TraversalStrategySerializerTest.java | 91 ++++++++ 21 files changed, 876 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 140c3d057f..675bae3077 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -31,6 +31,7 @@ This release also includes changes from prior 3.7.x releases. * Bumped to Groovy 4.0.32 which adds support for parsing Java 25 bytecode. * Bumped Hadoop to 3.4.3 (and Kerby to 2.0.3) to enable `hadoop-gremlin` to build and run on Java 25. * Add missing `Configuring` interface to `GraphStepPlaceholder` and `VertexStepPlaceholder` +* Restricted typed GraphSON and GraphBinary `TraversalStrategy` deserialization to TinkerPop strategies and explicitly allowed provider strategies. * Fixed bug in `group()` value traversal where keys were retained with stale barrier state instead of being filtered when steps following a `Barrier` in the second `by()` produced no output (e.g. `by(values("age").fold().unfold())` or `by(__.out().fold().count(local).is(P.gt(0)))` for vertices with no out-edges). * Fixed bug in `gremlin-javascript` GraphBinary and GraphSON deserialization where `OffsetDateTime` values outside the JavaScript `Date` range were silently returned as invalid `Date` objects instead of failing deserialization. * 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`. diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc index 77e71d3917..ff47f8ff9b 100644 --- a/docs/src/reference/gremlin-applications.asciidoc +++ b/docs/src/reference/gremlin-applications.asciidoc @@ -1216,8 +1216,15 @@ Gremlin Server is configured by default with GraphSON 3.0 as shown above. It has |========================================================= |Key |Description |Default |ioRegistries |A list of `IoRegistry` implementations to be applied to the serializer. |_none_ +|allowedTraversalStrategies |A list of fully qualified `TraversalStrategy` class names that may be deserialized from typed GraphSON request or response data, in addition to TinkerPop's built-in strategies. |_none_ |========================================================= +Typed GraphSON accepts serialized `TraversalStrategy` values only for TinkerPop's built-in strategies and classes named +in `allowedTraversalStrategies`. This setting applies to custom provider strategies that cross a remote serialization +boundary in request or response data. A custom strategy that is not listed is rejected during deserialization. The same +allow-list is available in programmatic configuration with +`GraphSONMapper.Builder.addAllowedTraversalStrategy(Class<? extends TraversalStrategy>)`. + 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 @@ -1271,8 +1278,19 @@ It has the MIME type of `application/vnd.graphbinary-v1.0` and the following con |custom |A list of classes with custom kryo `Serializer` implementations related to them in the form of `<class>;<serializer-class>`. |_none_ |ioRegistries |A list of `IoRegistry` implementations to be applied to the serializer. |_none_ |builder |Name of the `TypeSerializerRegistry.Builder` instance to be used to construct the `TypeSerializerRegistry`. |_none_ +|allowedTraversalStrategies |A list of fully qualified `TraversalStrategy` class names that may be deserialized from GraphBinary request or response data, in addition to TinkerPop's built-in strategies. |_none_ |========================================================= +GraphBinary accepts serialized `TraversalStrategy` values only for TinkerPop's built-in strategies and classes named in +`allowedTraversalStrategies`. This setting applies to custom provider strategies that cross a remote serialization +boundary in request or response data. A custom strategy that is not listed is rejected during deserialization. The same +allow-list is available in programmatic configuration with +`TypeSerializerRegistry.Builder.addAllowedTraversalStrategy(Class<? extends TraversalStrategy>)`. + +When a custom `builder` pre-registers the standard `TraversalStrategySerializer`, entries from +`allowedTraversalStrategies` are merged into that serializer's allow-list. If the custom builder registers a different +serializer for `TraversalStrategy`, configuration fails because the allow-list cannot be safely applied. + As described above, there are multiple ways in which to register serializers for GraphBinary-based serialization. Note that the `ioRegistries` setting is applied first, followed by the `custom` setting. @@ -3179,4 +3197,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..df5117f3bb 100644 --- a/docs/src/upgrade/release-3.8.2.asciidoc +++ b/docs/src/upgrade/release-3.8.2.asciidoc @@ -77,3 +77,60 @@ Map<String, List<Property<Object>>> propertyMap = vertex.propertyMap(); ---- See: link:https://issues.apache.org/jira/browse/TINKERPOP-3187[TINKERPOP-3187] + +==== TraversalStrategy Deserialization Allow-List + +Typed GraphSON 2.0, typed GraphSON 3.0 and GraphBinary now restrict `TraversalStrategy` deserialization to built-in +TinkerPop strategies and strategies that have been explicitly allowed. This change prevents untrusted request or +response data from naming arbitrary JVM classes in a serialized `g:TraversalStrategy`. + +Users who send or receive custom `TraversalStrategy` implementations over these formats must add those strategy classes +to the serializer configuration. If a custom strategy is not registered, deserialization will fail with an error +indicating that the `TraversalStrategy` class is not allowed. + +For embedded or programmatic serializer configuration, add the strategies to the relevant mapper or registry builder: + +[source,text] +---- +GraphSONMapper graphSONMapper = GraphSONMapper.build(). + addAllowedTraversalStrategy(MyStrategy.class). + create(); + +TypeSerializerRegistry graphBinaryRegistry = TypeSerializerRegistry.build(). + addAllowedTraversalStrategy(MyStrategy.class). + create(); +---- + +Only register strategy classes that are intended to be loadable from remote data and are safe to construct from their +serialized configuration. + +=== Upgrading for Providers + +==== Graph System Providers + +===== TraversalStrategy Deserialization Allow-List + +Gremlin Server based remote providers that define custom `TraversalStrategy` classes and expect them to cross the +client/server boundary must register those classes with the serializers used by their clients and servers. Built-in +TinkerPop strategies remain available by default, but custom strategies are no longer deserialized solely because +their fully qualified class name appears in `g:TraversalStrategy` data. + +Providers that package Gremlin Server configuration should add their custom strategy classes to the serializer +configuration with `allowedTraversalStrategies`. + +[source,text] +---- +serializers: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, + config: { allowedTraversalStrategies: [com.example.MyStrategy] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, + config: { allowedTraversalStrategies: [com.example.MyStrategy] }} +---- + +Providers that construct serializers programmatically should call `GraphSONMapper.Builder.addAllowedTraversalStrategy()` +for GraphSON and `TypeSerializerRegistry.Builder.addAllowedTraversalStrategy()` for GraphBinary before creating the +mapper or registry. + +For GraphBinary, configured strategies are merged with a standard `TraversalStrategySerializer` registered by a custom +`TypeSerializerRegistry.Builder`. A custom builder that registers a different serializer for `TraversalStrategy` cannot +also use `allowedTraversalStrategies`, because TinkerPop cannot safely apply the allow-list to that serializer. 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 baca8dc8c3..f4dd62c276 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 @@ -31,6 +31,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTra import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.BytecodeHelper; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; @@ -184,6 +185,9 @@ 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 (!TraversalStrategyResolver.isGloballyAllowed(strategyClass)) + throw new IllegalStateException(String.format("TraversalStrategy class is not allowed: %s", 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/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TraversalStrategyResolver.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TraversalStrategyResolver.java new file mode 100644 index 0000000000..2096a97587 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TraversalStrategyResolver.java @@ -0,0 +1,258 @@ +/* + * 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.process.traversal.strategy; + +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.TraversalStrategy; +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.ByModulatorOptimizationStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.FilterRankingStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.GValueReductionStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.IdentityRemovalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.IncidentToAdjacentStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.InlineFilterStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.LazyBarrierStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.MatchPredicateStrategy; +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.provider.ProviderGValueReductionStrategy; +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 java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Resolves traversal strategy class names supplied by typed GraphSON and GraphBinary payloads against an explicit + * registry. Deserializers use this class instead of direct class loading so that an incoming {@code fqcn} can only + * name a strategy that is part of TinkerPop's built-in set or one that a provider added to the resolver. + * <p> + * There are two validation points for traversal strategy proxies. The serializer-specific resolver validates the + * strategy name while bytes are being decoded. {@code JavaTranslator} later validates the resulting + * {@link TraversalStrategyProxy} before invoking {@code instance()} or {@code create(Configuration)} on the strategy + * class. The second check cannot refer back to the serializer instance that produced the proxy, so created resolvers + * also contribute their allowed classes to a process-wide set used by {@code JavaTranslator}. + */ +public final class TraversalStrategyResolver { + + private static final Collection<Class<? extends TraversalStrategy>> DEFAULT_STRATEGIES = Collections.unmodifiableList(Arrays.asList( + ConnectiveStrategy.class, + ElementIdStrategy.class, + EventStrategy.class, + HaltedTraverserStrategy.class, + OptionsStrategy.class, + PartitionStrategy.class, + RequirementsStrategy.class, + SackStrategy.class, + SubgraphStrategy.class, + SeedStrategy.class, + SideEffectStrategy.class, + LazyBarrierStrategy.class, + MatchAlgorithmStrategy.class, + AdjacentToIncidentStrategy.class, + ByModulatorOptimizationStrategy.class, + ProductiveByStrategy.class, + CountStrategy.class, + GValueReductionStrategy.class, + FilterRankingStrategy.class, + IdentityRemovalStrategy.class, + IncidentToAdjacentStrategy.class, + InlineFilterStrategy.class, + MatchPredicateStrategy.class, + OrderLimitStrategy.class, + PathProcessorStrategy.class, + PathRetractionStrategy.class, + RepeatUnrollStrategy.class, + ProviderGValueReductionStrategy.class, + ComputerVerificationStrategy.class, + LambdaRestrictionStrategy.class, + ReadOnlyStrategy.class, + StandardVerificationStrategy.class, + EarlyLimitStrategy.class, + EdgeLabelVerificationStrategy.class, + ReservedKeysVerificationStrategy.class, + ReferenceElementStrategy.class, + ComputerFinalizationStrategy.class, + MessagePassingReductionStrategy.class, + ProfileStrategy.class, + VertexProgramRestrictionStrategy.class, + GraphFilterStrategy.class, + VertexProgramStrategy.class, + RemoteStrategy.class + )); + + /* + * The global set is the union of strategy classes allowed by resolvers created in this JVM. It is not used by + * deserializers, which validate against their own resolver instance. It exists for JavaTranslator, where bytecode + * already contains a TraversalStrategyProxy and there is no serializer-local resolver to consult before reflective + * strategy creation. + */ + private static final Set<Class<? extends TraversalStrategy>> GLOBALLY_ALLOWED_STRATEGIES = ConcurrentHashMap.newKeySet(); + private static final TraversalStrategyResolver DEFAULT_RESOLVER = build().create(); + + private final Map<String, Class<? extends TraversalStrategy>> strategiesByName; + + private TraversalStrategyResolver(final Collection<Class<? extends TraversalStrategy>> allowedStrategies) { + final Map<String, Class<? extends TraversalStrategy>> strategies = new LinkedHashMap<>(); + allowedStrategies.forEach(strategy -> strategies.put(strategy.getName(), strategy)); + this.strategiesByName = Collections.unmodifiableMap(strategies); + } + + /** + * Creates a builder initialized with the default TinkerPop traversal strategy classes. + */ + public static Builder build() { + return new Builder(); + } + + /** + * Gets the resolver for TinkerPop's default traversal strategy classes. + */ + public static TraversalStrategyResolver defaultResolver() { + return DEFAULT_RESOLVER; + } + + /** + * Determines whether a traversal strategy class has been allowed by any resolver created in this JVM. This check is + * intended for code paths such as {@code JavaTranslator} that receive a {@link TraversalStrategyProxy} after + * deserialization has already completed. + */ + public static boolean isGloballyAllowed(final Class<?> strategyClass) { + return GLOBALLY_ALLOWED_STRATEGIES.contains(strategyClass); + } + + /** + * Resolves a fully qualified class name to an allowed traversal strategy class. + * + * @throws IllegalArgumentException if the class name is not present in this resolver + */ + public Class<? extends TraversalStrategy> resolve(final String fqcn) { + final Class<? extends TraversalStrategy> strategyClass = strategiesByName.get(fqcn); + if (null == strategyClass) + throw new IllegalArgumentException(String.format("TraversalStrategy class is not allowed: %s", fqcn)); + + return strategyClass; + } + + /** + * Determines whether the supplied class is allowed by this resolver. + */ + public boolean isAllowed(final Class<?> strategyClass) { + return null != strategyClass && strategiesByName.get(strategyClass.getName()) == strategyClass; + } + + /** + * Gets the traversal strategy classes allowed by this resolver. + */ + public Collection<Class<? extends TraversalStrategy>> getAllowedStrategies() { + return strategiesByName.values(); + } + + private static Optional<Class<? extends TraversalStrategy>> tryLoadSparqlStrategy() { + try { + final Class<?> sparqlStrategy = Class.forName( + "org.apache.tinkerpop.gremlin.sparql.process.traversal.strategy.SparqlStrategy", + false, + TraversalStrategyResolver.class.getClassLoader()); + if (TraversalStrategy.class.isAssignableFrom(sparqlStrategy)) + return Optional.of((Class<? extends TraversalStrategy>) sparqlStrategy); + } catch (Exception ignored) { + // optional module + } + + return Optional.empty(); + } + + /** + * Builds a traversal strategy resolver. Providers can add custom traversal strategy classes here so that remote + * GraphSON and GraphBinary deserialization can resolve them. + */ + public static final class Builder { + private final Map<String, Class<? extends TraversalStrategy>> allowedStrategies = new LinkedHashMap<>(); + + private Builder() { + DEFAULT_STRATEGIES.forEach(this::addAllowedTraversalStrategy); + tryLoadSparqlStrategy().ifPresent(this::addAllowedTraversalStrategy); + } + + /** + * Adds a traversal strategy class that can be resolved during deserialization. + */ + public Builder addAllowedTraversalStrategy(final Class<? extends TraversalStrategy> strategyClass) { + if (null == strategyClass) + throw new IllegalArgumentException("The traversal strategy class cannot be null"); + + allowedStrategies.put(strategyClass.getName(), strategyClass); + return this; + } + + /** + * Adds traversal strategy classes that can be resolved during deserialization. + */ + public Builder addAllowedTraversalStrategies(final Collection<Class<? extends TraversalStrategy>> strategyClasses) { + strategyClasses.forEach(this::addAllowedTraversalStrategy); + return this; + } + + /** + * Creates the resolver and records the allowed strategy classes for later validation by {@code JavaTranslator}. + * The resolver-local allow-list is used during deserialization. The recorded global allow-list is used later + * when bytecode translation reaches a {@link TraversalStrategyProxy} without access to the serializer-local + * resolver that created it. + */ + public TraversalStrategyResolver create() { + GLOBALLY_ALLOWED_STRATEGIES.addAll(allowedStrategies.values()); + return new TraversalStrategyResolver(allowedStrategies.values()); + } + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java index f885a70723..316c9ef759 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java @@ -33,6 +33,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.process.traversal.TextP; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; @@ -189,7 +190,6 @@ public class TypeSerializerRegistry { new RegistryEntry<>(ByteBuffer.class, new ByteBufferSerializer()), new RegistryEntry<>(Short.class, SingleTypeSerializer.ShortSerializer), new RegistryEntry<>(Boolean.class, SingleTypeSerializer.BooleanSerializer), - new RegistryEntry<>(TraversalStrategy.class, new TraversalStrategySerializer()), new RegistryEntry<>(BulkSet.class, new BulkSetSerializer()), new RegistryEntry<>(Tree.class, new TreeSerializer()), new RegistryEntry<>(Metrics.class, new MetricsSerializer()), @@ -224,6 +224,7 @@ public class TypeSerializerRegistry { public static class Builder { private final List<RegistryEntry> list = new LinkedList<>(); private Function<Class<?>, TypeSerializer<?>> fallbackResolver; + private final List<Class<? extends TraversalStrategy>> allowedTraversalStrategies = new LinkedList<>(); /** * Adds a serializer for a built-in type. @@ -281,6 +282,14 @@ public class TypeSerializerRegistry { return this; } + /** + * Allow a provider supplied {@link TraversalStrategy} to be deserialized from GraphBinary. + */ + public Builder addAllowedTraversalStrategy(final Class<? extends TraversalStrategy> strategyClass) { + this.allowedTraversalStrategies.add(strategyClass); + return this; + } + /** * Add {@link CustomTypeSerializer} by way of an {@link IoRegistry}. The registry entries should be bound to * {@link GraphBinaryIo}. @@ -300,7 +309,33 @@ public class TypeSerializerRegistry { * Creates a new {@link TypeSerializerRegistry} instance based on the serializers added. */ public TypeSerializerRegistry create() { - return new TypeSerializerRegistry(list, fallbackResolver); + final List<RegistryEntry> entries = new LinkedList<>(list); + boolean hasTraversalStrategySerializer = false; + for (int ix = 0; ix < entries.size(); ix++) { + final RegistryEntry entry = entries.get(ix); + if (entry.getType() != TraversalStrategy.class) + continue; + + hasTraversalStrategySerializer = true; + if (allowedTraversalStrategies.isEmpty()) + continue; + + if (!(entry.getTypeSerializer() instanceof TraversalStrategySerializer)) + throw new IllegalStateException("The allowed traversal strategies cannot be applied to the " + + "custom TraversalStrategy serializer"); + + final TraversalStrategySerializer serializer = (TraversalStrategySerializer) entry.getTypeSerializer(); + entries.set(ix, new RegistryEntry<>(TraversalStrategy.class, + serializer.withAllowedTraversalStrategies(allowedTraversalStrategies))); + } + + if (!hasTraversalStrategySerializer) { + final TraversalStrategyResolver traversalStrategyResolver = TraversalStrategyResolver.build(). + addAllowedTraversalStrategies(allowedTraversalStrategies).create(); + entries.add(new RegistryEntry<>(TraversalStrategy.class, new TraversalStrategySerializer(traversalStrategyResolver))); + } + + return new TypeSerializerRegistry(entries, fallbackResolver); } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TraversalStrategySerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TraversalStrategySerializer.java index 7790b213ea..eaa7d73b98 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TraversalStrategySerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TraversalStrategySerializer.java @@ -26,9 +26,11 @@ import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; 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.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.structure.io.Buffer; import java.io.IOException; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -37,13 +39,32 @@ import java.util.Map; */ public class TraversalStrategySerializer extends SimpleTypeSerializer<TraversalStrategy> { + private final TraversalStrategyResolver traversalStrategyResolver; + public TraversalStrategySerializer() { + this(TraversalStrategyResolver.defaultResolver()); + } + + public TraversalStrategySerializer(final TraversalStrategyResolver traversalStrategyResolver) { super(DataType.TRAVERSALSTRATEGY); + this.traversalStrategyResolver = traversalStrategyResolver; + } + + /** + * Creates a serializer that preserves this serializer's allowed strategies and adds the supplied strategies. + */ + public TraversalStrategySerializer withAllowedTraversalStrategies( + final Collection<Class<? extends TraversalStrategy>> strategyClasses) { + final TraversalStrategyResolver resolver = TraversalStrategyResolver.build(). + addAllowedTraversalStrategies(traversalStrategyResolver.getAllowedStrategies()). + addAllowedTraversalStrategies(strategyClasses).create(); + return new TraversalStrategySerializer(resolver); } @Override protected TraversalStrategy readValue(final Buffer buffer, final GraphBinaryReader context) throws IOException { - final Class<TraversalStrategy> clazz = context.readValue(buffer, Class.class, false); + final String strategyClassName = context.readValue(buffer, String.class, false); + final Class<? extends TraversalStrategy> clazz = traversalStrategyResolver.resolve(strategyClassName); final Map config = context.readValue(buffer, Map.class, false); return new TraversalStrategyProxy(clazz, new MapConfiguration(config)); @@ -51,7 +72,10 @@ public class TraversalStrategySerializer extends SimpleTypeSerializer<TraversalS @Override protected void writeValue(final TraversalStrategy value, final Buffer buffer, final GraphBinaryWriter context) throws IOException { - context.writeValue(value.getClass(), buffer, false); + final Class<? extends TraversalStrategy> strategyClass = value instanceof TraversalStrategyProxy ? + ((TraversalStrategyProxy) value).getStrategyClass() : + value.getClass(); + context.writeValue(strategyClass.getName(), buffer, false); context.writeValue(translateToBytecode(ConfigurationConverter.getMap(value.getConfiguration())), buffer, false); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapper.java index cda357caae..b8991d2ffa 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapper.java @@ -18,6 +18,8 @@ */ package org.apache.tinkerpop.gremlin.structure.io.graphson; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.io.IoRegistry; import org.apache.tinkerpop.gremlin.structure.io.Mapper; @@ -71,6 +73,7 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { private final GraphSONVersion version; private final TypeInfo typeInfo; private final StreamReadConstraints streamReadConstraints; + private final TraversalStrategyResolver traversalStrategyResolver; private GraphSONMapper(final Builder builder) { this.customModules = builder.customModules; @@ -79,6 +82,8 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { this.version = builder.version; this.streamReadConstraints = builder.streamReadConstraintsBuilder.build(); this.typeInfo = builder.typeInfo; + this.traversalStrategyResolver = TraversalStrategyResolver.build(). + addAllowedTraversalStrategies(builder.allowedTraversalStrategies).create(); } @Override @@ -86,7 +91,7 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { final ObjectMapper om = new ObjectMapper(JsonFactory.builder().streamReadConstraints(streamReadConstraints).build()); om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - final GraphSONModule graphSONModule = version.getBuilder().create(normalize, typeInfo); + final GraphSONModule graphSONModule = version.getBuilder().create(normalize, typeInfo, traversalStrategyResolver); om.registerModule(graphSONModule); customModules.forEach(om::registerModule); @@ -174,6 +179,7 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { builder.normalize = mapper.normalize; builder.typeInfo = mapper.typeInfo; builder.streamReadConstraintsBuilder = mapper.streamReadConstraints.rebuild(); + builder.allowedTraversalStrategies = new ArrayList<>(mapper.traversalStrategyResolver.getAllowedStrategies()); return builder; } @@ -196,6 +202,7 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { public static class Builder implements Mapper.Builder<Builder> { private List<SimpleModule> customModules = new ArrayList<>(); private List<GraphSONModule.GraphSONModuleBuilder> customModuleBuilders = new ArrayList<>(); + private List<Class<? extends TraversalStrategy>> allowedTraversalStrategies = new ArrayList<>(); private boolean loadCustomModules = false; private boolean normalize = false; private List<IoRegistry> registries = new ArrayList<>(); @@ -252,6 +259,14 @@ public class GraphSONMapper implements Mapper<ObjectMapper> { return this; } + /** + * Allow a provider supplied {@link TraversalStrategy} to be deserialized from GraphSON. + */ + public Builder addAllowedTraversalStrategy(final Class<? extends TraversalStrategy> strategyClass) { + this.allowedTraversalStrategies.add(strategyClass); + return this; + } + /** * Supply a default extension module of V2_0 and V3_0 for serialization/deserialization. */ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java index b3d0443131..5a2ed4baeb 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java @@ -43,6 +43,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; 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; @@ -137,7 +138,10 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { */ private static Optional<Class<?>> tryLoadSparqlStrategy() { try { - return Optional.of(Class.forName("org.apache.tinkerpop.gremlin.sparql.process.traversal.strategy.SparqlStrategy")); + return Optional.of(Class.forName( + "org.apache.tinkerpop.gremlin.sparql.process.traversal.strategy.SparqlStrategy", + false, + GraphSONModule.class.getClassLoader())); } catch (Exception ignored) { return Optional.empty(); } @@ -244,6 +248,11 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { * Constructs a new object. */ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { + this(normalize, typeInfo, TraversalStrategyResolver.defaultResolver()); + } + + protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo, + final TraversalStrategyResolver traversalStrategyResolver) { super("graphson-3.0"); /////////////////////// SERIALIZERS //////////////////////////// @@ -307,7 +316,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { addDeserializer(Metrics.class, new GraphSONSerializersV3.MetricsJacksonDeserializer()); addDeserializer(TraversalMetrics.class, new GraphSONSerializersV3.TraversalMetricsJacksonDeserializer()); addDeserializer(Tree.class, new GraphSONSerializersV3.TreeJacksonDeserializer()); - addDeserializer(TraversalStrategyProxy.class, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer<>()); + addDeserializer(TraversalStrategyProxy.class, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer<>(traversalStrategyResolver)); // java.util - use the standard jackson serializers for collections when types aren't embedded if (typeInfo != TypeInfo.NO_TYPES) { @@ -380,9 +389,9 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { // GraphFilterStrategy.class, VertexProgramStrategy.class - ).forEach(strategy -> addDeserializer(strategy, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer(strategy))); + ).forEach(strategy -> addDeserializer(strategy, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer(strategy, traversalStrategyResolver))); - GraphSONModule.tryLoadSparqlStrategy().ifPresent(s -> addDeserializer(s, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer(s))); + GraphSONModule.tryLoadSparqlStrategy().ifPresent(s -> addDeserializer(s, new TraversalSerializersV3.TraversalStrategyProxyJacksonDeserializer(s, traversalStrategyResolver))); } public static Builder build() { @@ -409,6 +418,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { return new GraphSONModuleV3(normalize, typeInfo); } + @Override + public GraphSONModule create(final boolean normalize, final TypeInfo typeInfo, + final TraversalStrategyResolver traversalStrategyResolver) { + return new GraphSONModuleV3(normalize, typeInfo, traversalStrategyResolver); + } + } } @@ -508,6 +523,10 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { * Constructs a new object. */ protected GraphSONModuleV2(final boolean normalize) { + this(normalize, TraversalStrategyResolver.defaultResolver()); + } + + protected GraphSONModuleV2(final boolean normalize, final TraversalStrategyResolver traversalStrategyResolver) { super("graphson-2.0"); /////////////////////// SERIALIZERS //////////////////////////// @@ -565,7 +584,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { addDeserializer(Metrics.class, new GraphSONSerializersV2.MetricsJacksonDeserializer()); addDeserializer(TraversalMetrics.class, new GraphSONSerializersV2.TraversalMetricsJacksonDeserializer()); addDeserializer(Tree.class, new GraphSONSerializersV2.TreeJacksonDeserializer()); - addDeserializer(TraversalStrategyProxy.class, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer<>()); + addDeserializer(TraversalStrategyProxy.class, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer<>(traversalStrategyResolver)); // numbers addDeserializer(Integer.class, new GraphSONSerializersV2.IntegerJacksonDeserializer()); @@ -630,9 +649,9 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { // GraphFilterStrategy.class, VertexProgramStrategy.class - ).forEach(strategy -> addDeserializer(strategy, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer(strategy))); + ).forEach(strategy -> addDeserializer(strategy, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer(strategy, traversalStrategyResolver))); - GraphSONModule.tryLoadSparqlStrategy().ifPresent(s -> addDeserializer(s, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer(s))); + GraphSONModule.tryLoadSparqlStrategy().ifPresent(s -> addDeserializer(s, new TraversalSerializersV2.TraversalStrategyProxyJacksonDeserializer(s, traversalStrategyResolver))); } public static Builder build() { @@ -659,6 +678,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { return new GraphSONModuleV2(normalize); } + @Override + public GraphSONModule create(final boolean normalize, final TypeInfo typeInfo, + final TraversalStrategyResolver traversalStrategyResolver) { + return new GraphSONModuleV2(normalize, traversalStrategyResolver); + } + } } @@ -758,5 +783,10 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { * @param typeInfo allows the module to react to the specified typeinfo given to the mapper */ GraphSONModule create(final boolean normalize, final TypeInfo typeInfo); + + default GraphSONModule create(final boolean normalize, final TypeInfo typeInfo, + final TraversalStrategyResolver traversalStrategyResolver) { + return create(normalize, typeInfo); + } } } 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..8b88514ff3 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 @@ -31,6 +31,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; 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; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; @@ -523,15 +524,27 @@ final class TraversalSerializersV2 { final static class TraversalStrategyProxyJacksonDeserializer<T extends TraversalStrategy> extends AbstractObjectDeserializer<TraversalStrategyProxy> { private Class<T> clazz; + private final TraversalStrategyResolver traversalStrategyResolver; public TraversalStrategyProxyJacksonDeserializer() { + this(TraversalStrategyResolver.defaultResolver()); + } + + public TraversalStrategyProxyJacksonDeserializer(final TraversalStrategyResolver traversalStrategyResolver) { super(TraversalStrategyProxy.class); this.clazz = null; + this.traversalStrategyResolver = traversalStrategyResolver; } public TraversalStrategyProxyJacksonDeserializer(final Class<T> clazz) { + this(clazz, TraversalStrategyResolver.defaultResolver()); + } + + public TraversalStrategyProxyJacksonDeserializer(final Class<T> clazz, + final TraversalStrategyResolver traversalStrategyResolver) { super(TraversalStrategyProxy.class); this.clazz = clazz; + this.traversalStrategyResolver = traversalStrategyResolver; } @Override @@ -544,11 +557,10 @@ 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 Object fqcn = data.get("fqcn"); + if (null == fqcn) + throw new IllegalArgumentException("TraversalStrategy fqcn is required"); + clasz = traversalStrategyResolver.resolve(fqcn.toString()); } 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..77cc153b1f 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 @@ -32,6 +32,7 @@ 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; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyResolver; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; @@ -616,15 +617,27 @@ final class TraversalSerializersV3 { final static class TraversalStrategyProxyJacksonDeserializer<T extends TraversalStrategy> extends AbstractObjectDeserializer<TraversalStrategyProxy> { private Class<T> clazz; + private final TraversalStrategyResolver traversalStrategyResolver; public TraversalStrategyProxyJacksonDeserializer() { + this(TraversalStrategyResolver.defaultResolver()); + } + + public TraversalStrategyProxyJacksonDeserializer(final TraversalStrategyResolver traversalStrategyResolver) { super(TraversalStrategyProxy.class); this.clazz = null; + this.traversalStrategyResolver = traversalStrategyResolver; } public TraversalStrategyProxyJacksonDeserializer(final Class<T> clazz) { + this(clazz, TraversalStrategyResolver.defaultResolver()); + } + + public TraversalStrategyProxyJacksonDeserializer(final Class<T> clazz, + final TraversalStrategyResolver traversalStrategyResolver) { super(TraversalStrategyProxy.class); this.clazz = clazz; + this.traversalStrategyResolver = traversalStrategyResolver; } @Override @@ -637,11 +650,10 @@ 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 Object fqcn = data.get("fqcn"); + if (null == fqcn) + throw new IllegalArgumentException("TraversalStrategy fqcn is required"); + clasz = traversalStrategyResolver.resolve(fqcn.toString()); } else { clasz = this.clazz; } 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 index 13a9232158..472e258dcf 100644 --- 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 @@ -22,11 +22,18 @@ package org.apache.tinkerpop.gremlin.jsr223; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TestTraversalStrategies.UnregisteredTraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +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.instanceOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class JavaTranslatorTest { private GraphTraversalSource g = EmptyGraph.instance().traversal(); @@ -86,4 +93,28 @@ public class JavaTranslatorTest { assertEquals(g.E().has("weight", (String) null).asAdmin(), translation); } -} \ No newline at end of file + @Test + public void shouldTranslateRegisteredTraversalStrategyProxy() { + final Bytecode bytecode = new Bytecode(); + bytecode.addSource(TraversalSource.Symbols.withStrategies, new TraversalStrategyProxy(ReadOnlyStrategy.instance())); + bytecode.addStep("V"); + final Traversal.Admin<?, ?> translation = translator.translate(bytecode); + assertEquals(g.withStrategies(ReadOnlyStrategy.instance()).V().asAdmin(), translation); + } + + @Test + public void shouldRejectUnregisteredTraversalStrategyProxy() { + final Bytecode bytecode = new Bytecode(); + bytecode.addSource(TraversalSource.Symbols.withStrategies, new TraversalStrategyProxy(UnregisteredTraversalStrategy.instance())); + bytecode.addStep("V"); + + try { + translator.translate(bytecode); + fail("Should have rejected an unregistered traversal strategy proxy"); + } catch (Exception ex) { + assertThat(ex, instanceOf(IllegalStateException.class)); + assertEquals("TraversalStrategy class is not allowed: " + UnregisteredTraversalStrategy.class.getName(), ex.getMessage()); + } + } + +} diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TestTraversalStrategies.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TestTraversalStrategies.java new file mode 100644 index 0000000000..46e245ea89 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TestTraversalStrategies.java @@ -0,0 +1,79 @@ +/* + * 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.process.traversal.strategy; + +import org.apache.commons.configuration2.BaseConfiguration; +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.util.GremlinDisabledListDelimiterHandler; + +public final class TestTraversalStrategies { + + private TestTraversalStrategies() { + } + + public static class DummyTraversalStrategy extends AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy> + implements TraversalStrategy.DecorationStrategy { + private static final DummyTraversalStrategy INSTANCE = new DummyTraversalStrategy(); + + protected DummyTraversalStrategy() { + } + + public static DummyTraversalStrategy instance() { + return INSTANCE; + } + + @Override + public void apply(final Traversal.Admin<?, ?> traversal) { + // do nothing + } + } + + public static final class DummyConfiguredTraversalStrategy extends DummyTraversalStrategy { + private static final DummyConfiguredTraversalStrategy INSTANCE = new DummyConfiguredTraversalStrategy(); + + private DummyConfiguredTraversalStrategy() { + } + + public static DummyConfiguredTraversalStrategy instance() { + return INSTANCE; + } + + @Override + public Configuration getConfiguration() { + final BaseConfiguration conf = new BaseConfiguration(); + conf.setListDelimiterHandler(GremlinDisabledListDelimiterHandler.instance()); + conf.setProperty("x", 123); + conf.setProperty("y", "test"); + return conf; + } + } + + public static final class UnregisteredTraversalStrategy extends DummyTraversalStrategy { + private static final UnregisteredTraversalStrategy INSTANCE = new UnregisteredTraversalStrategy(); + + private UnregisteredTraversalStrategy() { + } + + public static UnregisteredTraversalStrategy instance() { + return INSTANCE; + } + } +} 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..1a1bcf6a5c 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 @@ -18,27 +18,25 @@ */ package org.apache.tinkerpop.gremlin.structure.io.graphson; -import org.apache.commons.configuration2.BaseConfiguration; -import org.apache.commons.configuration2.Configuration; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; 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.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TestTraversalStrategies.DummyConfiguredTraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TestTraversalStrategies.DummyTraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.HaltedTraverserStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; -import org.apache.tinkerpop.gremlin.util.GremlinDisabledListDelimiterHandler; import org.apache.tinkerpop.gremlin.util.function.Lambda; import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper; +import org.apache.tinkerpop.shaded.jackson.databind.exc.MismatchedInputException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -73,8 +71,10 @@ import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; /** @@ -89,9 +89,13 @@ public class GraphSONMapperEmbeddedTypeTest extends AbstractGraphSONTest { {"v1", GraphSONMapper.build().version(GraphSONVersion.V1_0).typeInfo(TypeInfo.PARTIAL_TYPES).create().createMapper()}, {"v2", GraphSONMapper.build().version(GraphSONVersion.V2_0) .addCustomModule(GraphSONXModuleV2.build()) + .addAllowedTraversalStrategy(DummyTraversalStrategy.class) + .addAllowedTraversalStrategy(DummyConfiguredTraversalStrategy.class) .typeInfo(TypeInfo.PARTIAL_TYPES).create().createMapper()}, {"v3", GraphSONMapper.build().version(GraphSONVersion.V3_0) .addCustomModule(GraphSONXModuleV3.build()) + .addAllowedTraversalStrategy(DummyTraversalStrategy.class) + .addAllowedTraversalStrategy(DummyConfiguredTraversalStrategy.class) .typeInfo(TypeInfo.PARTIAL_TYPES).create().createMapper()} }); } @@ -533,48 +537,24 @@ public class GraphSONMapperEmbeddedTypeTest extends AbstractGraphSONTest { } @Test - public void shouldHandlePExt() throws Exception { - assumeThat(version, either(startsWith("v2")).or(startsWith("v3"))); - - final P o = PExt.mix("bah"); - assertEquals(o, serializeDeserialize(mapper, o, P.class)); - } - - public static class DummyTraversalStrategy implements TraversalStrategy { - - private static final DummyTraversalStrategy INSTANCE = new DummyTraversalStrategy(); - - @Override - public void apply(final Traversal.Admin traversal) { - // do nothing - } - - @Override - public int compareTo(final Object o) { - return 0; - } + public void shouldRejectUnregisteredTraversalStrategyProxy() throws Exception { + assumeThat(version, either(startsWith("v2")).or(startsWith("v3"))); - public static DummyTraversalStrategy instance() { - return INSTANCE; + try { + mapper.readValue("{\"@type\":\"g:TraversalStrategy\",\"@value\":{\"conf\":{},\"fqcn\":\"java.lang.Runtime\"}}", TraversalStrategyProxy.class); + fail("Should have rejected an unregistered traversal strategy proxy"); + } catch (Exception ex) { + assertThat(ex, instanceOf(MismatchedInputException.class)); + assertThat(ex.getMessage(), containsString("TraversalStrategy class is not allowed: java.lang.Runtime")); } } - public static class DummyConfiguredTraversalStrategy extends DummyTraversalStrategy { - - private static final DummyConfiguredTraversalStrategy INSTANCE = new DummyConfiguredTraversalStrategy(); - - @Override - public Configuration getConfiguration() { - final BaseConfiguration conf = new BaseConfiguration(); - conf.setListDelimiterHandler(GremlinDisabledListDelimiterHandler.instance()); - conf.setProperty("x", 123); - conf.setProperty("y", "test"); - return conf; - } + @Test + public void shouldHandlePExt() throws Exception { + assumeThat(version, either(startsWith("v2")).or(startsWith("v3"))); - public static DummyConfiguredTraversalStrategy instance() { - return INSTANCE; - } + final P o = PExt.mix("bah"); + assertEquals(o, serializeDeserialize(mapper, o, P.class)); } public static class PExt<V> extends P<V> { diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV2.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV2.java index bf66da59e3..bdbc8f6aae 100644 --- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV2.java +++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV2.java @@ -74,6 +74,7 @@ public abstract class AbstractGraphSONMessageSerializerV2 extends AbstractMessag public void configure(final Map<String, Object> config, final Map<String, Graph> graphs) { final GraphSONMapper.Builder initialBuilder = initBuilder(null); addIoRegistries(config, initialBuilder); + addAllowedTraversalStrategies(config, initialBuilder); applyMaxTokenLimits(initialBuilder, config); mapper = configureBuilder(initialBuilder).create().createMapper(); } diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractMessageSerializer.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractMessageSerializer.java index 03f570aa5c..9fb368a3a4 100644 --- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractMessageSerializer.java +++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractMessageSerializer.java @@ -18,11 +18,15 @@ */ package org.apache.tinkerpop.gremlin.util.ser; -import org.apache.tinkerpop.gremlin.util.MessageSerializer; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.structure.io.IoRegistry; import org.apache.tinkerpop.gremlin.structure.io.Mapper; +import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; +import org.apache.tinkerpop.gremlin.util.MessageSerializer; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -35,6 +39,7 @@ import java.util.Map; */ public abstract class AbstractMessageSerializer<M> implements MessageSerializer<M> { public static final String TOKEN_IO_REGISTRIES = "ioRegistries"; + public static final String TOKEN_ALLOWED_TRAVERSAL_STRATEGIES = "allowedTraversalStrategies"; /** * Reads a list of fully qualified class names from the value of the {@link #TOKEN_IO_REGISTRIES} configuration @@ -84,6 +89,38 @@ public abstract class AbstractMessageSerializer<M> implements MessageSerializer< return instanceMethod; } + /** + * Reads a list of fully qualified {@link TraversalStrategy} class names that are allowed to be deserialized from + * typed request or response data. + */ + protected void addAllowedTraversalStrategies(final Map<String, Object> config, final GraphSONMapper.Builder builder) { + getTraversalStrategyClassesFromConfig(config).forEach(builder::addAllowedTraversalStrategy); + } + + /** + * Reads a list of fully qualified {@link TraversalStrategy} class names that are allowed to be deserialized from + * typed request or response data. + */ + protected void addAllowedTraversalStrategies(final Map<String, Object> config, final TypeSerializerRegistry.Builder builder) { + getTraversalStrategyClassesFromConfig(config).forEach(builder::addAllowedTraversalStrategy); + } + + private List<Class<? extends TraversalStrategy>> getTraversalStrategyClassesFromConfig(final Map<String, Object> config) { + final List<Class<? extends TraversalStrategy>> traversalStrategyClasses = new ArrayList<>(); + for (String className : getListStringFromConfig(TOKEN_ALLOWED_TRAVERSAL_STRATEGIES, config)) { + try { + final Class<?> strategyClass = Class.forName(className, false, this.getClass().getClassLoader()); + if (!TraversalStrategy.class.isAssignableFrom(strategyClass)) + throw new IllegalStateException(String.format("%s is not a TraversalStrategy", className)); + traversalStrategyClasses.add((Class<? extends TraversalStrategy>) strategyClass); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + return traversalStrategyClasses; + } + /** * Gets a {@link List} of strings from the configuration object. */ @@ -98,4 +135,4 @@ public abstract class AbstractMessageSerializer<M> implements MessageSerializer< return classNameList; } -} \ No newline at end of file +} diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV1.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV1.java index 5d7977b2ba..52a6d42d89 100644 --- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV1.java +++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV1.java @@ -126,6 +126,7 @@ public class GraphBinaryMessageSerializerV1 extends AbstractMessageSerializer<Gr } }); + addAllowedTraversalStrategies(config, builder); addCustomClasses(config, builder); this.serializeToString = Boolean.parseBoolean(config.getOrDefault(TOKEN_SERIALIZE_RESULT_TO_STRING, "false").toString()); diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/TestTraversalStrategies.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/TestTraversalStrategies.java new file mode 100644 index 0000000000..c78ca36dcb --- /dev/null +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/TestTraversalStrategies.java @@ -0,0 +1,46 @@ +/* + * 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.util; + +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; + +public final class TestTraversalStrategies { + + private TestTraversalStrategies() { + } + + public static final class DummyTraversalStrategy extends AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy> + implements TraversalStrategy.DecorationStrategy { + private static final DummyTraversalStrategy INSTANCE = new DummyTraversalStrategy(); + + private DummyTraversalStrategy() { + } + + public static DummyTraversalStrategy instance() { + return INSTANCE; + } + + @Override + public void apply(final Traversal.Admin<?, ?> traversal) { + // do nothing + } + } +} 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..0946a47f41 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,15 +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.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; -import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; -import org.apache.tinkerpop.gremlin.util.MessageSerializer; -import org.apache.tinkerpop.gremlin.util.Tokens; -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.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Property; @@ -37,14 +33,22 @@ import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV3; +import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.apache.tinkerpop.gremlin.util.MessageSerializer; +import org.apache.tinkerpop.gremlin.util.TestTraversalStrategies.DummyTraversalStrategy; +import org.apache.tinkerpop.gremlin.util.Tokens; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; +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.shaded.jackson.databind.JsonMappingException; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -403,6 +407,26 @@ public class GraphSONMessageSerializerV3Test { } } + @Test + public void shouldDeserializeConfiguredTraversalStrategy() throws SerializationException { + final GraphSONMessageSerializerV3 serializer = new GraphSONMessageSerializerV3(); + final Map<String, Object> config = new HashMap<>(); + config.put(AbstractMessageSerializer.TOKEN_ALLOWED_TRAVERSAL_STRATEGIES, + Collections.singletonList(DummyTraversalStrategy.class.getName())); + serializer.configure(config, Collections.emptyMap()); + + final String request = String.format("{\"requestId\":{\"@type\":\"g:UUID\",\"@value\":\"0397b9c0-ffab-470e-a6a8-644fc80c01d6\"},\"op\":\"bytecode\",\"processor\":\"traversal\",\"args\":{\"gremlin\":{\"@type\":\"g:Bytecode\",\"@value\":{\"source\":[[\"withStrategies\",{\"@type\":\"g:TraversalStrategy\",\"@value\":{\"fqcn\":\"%s\",\"conf\":{}}}]],\"step\":[[\"V\"]]}},\"aliases\":{\"g\":\"g\"}}}", + DummyTraversalStrategy.class.getName()); + final ByteBuf buffer = allocator.buffer(request.length()); + buffer.writeBytes(request.getBytes()); + + final RequestMessage deserialized = serializer.deserializeRequest(buffer); + final Bytecode deserializedBytecode = (Bytecode) deserialized.getArgs().get(Tokens.ARGS_GREMLIN); + final TraversalStrategyProxy strategy = (TraversalStrategyProxy) deserializedBytecode.getSourceInstructions().get(0).getArguments()[0]; + + assertEquals(DummyTraversalStrategy.class, strategy.getStrategyClass()); + } + private void assertCommon(final ResponseMessage response) { assertEquals(requestId, response.getRequestId()); assertEquals(ResponseStatusCode.SUCCESS, response.getStatus().getCode()); @@ -416,4 +440,5 @@ public class GraphSONMessageSerializerV3Test { private ResponseMessage convert(final Object toSerialize) throws SerializationException { return convert(toSerialize, this.serializer); } + } diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryMessageSerializerV1Test.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryMessageSerializerV1Test.java index b1e42d56ae..7986134a33 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryMessageSerializerV1Test.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryMessageSerializerV1Test.java @@ -20,16 +20,20 @@ package org.apache.tinkerpop.gremlin.util.ser.binary; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; 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.strategy.TraversalStrategyProxy; +import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry; +import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; +import org.apache.tinkerpop.gremlin.util.TestTraversalStrategies.DummyTraversalStrategy; import org.apache.tinkerpop.gremlin.util.Tokens; 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.util.ser.AbstractMessageSerializer; import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1; import org.apache.tinkerpop.gremlin.util.ser.SerializationException; -import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry; -import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; import org.junit.Test; import java.nio.charset.StandardCharsets; @@ -40,9 +44,11 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import static org.apache.tinkerpop.gremlin.util.MockitoHamcrestMatcherAdapter.reflectionEquals; -import static org.junit.Assert.assertEquals; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItemInArray; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class GraphBinaryMessageSerializerV1Test { private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; @@ -194,13 +200,47 @@ public class GraphBinaryMessageSerializerV1Test { assertEquals(1, counter); } - @Test(expected = IllegalStateException.class) + @Test public void shouldThrowWhenConfigurationOfRegistryBuilderFails() { final GraphBinaryMessageSerializerV1 serializer = new GraphBinaryMessageSerializerV1(); final Map<String, Object> config = new HashMap<>(); config.put(GraphBinaryMessageSerializerV1.TOKEN_BUILDER, "org.apache.tinkerpop.gremlin.util.ser.binary.NonExistentClass"); - serializer.configure(config, null); + try { + serializer.configure(config, null); + fail("Should have failed to configure a missing registry builder"); + } catch (Exception ex) { + assertThat(ex, instanceOf(IllegalStateException.class)); + assertEquals("java.lang.ClassNotFoundException: org.apache.tinkerpop.gremlin.util.ser.binary.NonExistentClass", ex.getMessage()); + } + } + + @Test + public void shouldDeserializeConfiguredTraversalStrategy() throws SerializationException { + final GraphBinaryMessageSerializerV1 serializer = new GraphBinaryMessageSerializerV1(); + final Map<String, Object> config = new HashMap<>(); + config.put(AbstractMessageSerializer.TOKEN_ALLOWED_TRAVERSAL_STRATEGIES, + Collections.singletonList(DummyTraversalStrategy.class.getName())); + serializer.configure(config, Collections.emptyMap()); + + final Bytecode bytecode = new Bytecode(); + bytecode.addSource("withStrategies", DummyTraversalStrategy.instance()); + bytecode.addStep("V"); + + final RequestMessage request = RequestMessage.build(Tokens.OPS_BYTECODE) + .processor("traversal") + .overrideRequestId(UUID.randomUUID()) + .addArg(Tokens.ARGS_GREMLIN, bytecode) + .create(); + final ByteBuf buffer = serializer.serializeRequestAsBinary(request, allocator); + final int mimeLength = buffer.readByte(); + buffer.skipBytes(mimeLength); + + final RequestMessage deserialized = serializer.deserializeRequest(buffer); + final Bytecode deserializedBytecode = (Bytecode) deserialized.getArgs().get(Tokens.ARGS_GREMLIN); + final TraversalStrategyProxy strategy = (TraversalStrategyProxy) deserializedBytecode.getSourceInstructions().get(0).getArguments()[0]; + + assertEquals(DummyTraversalStrategy.class, strategy.getStrategyClass()); } @Test @@ -276,4 +316,5 @@ public class GraphBinaryMessageSerializerV1Test { return super.create(); } } + } diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/TraversalStrategySerializerTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/TraversalStrategySerializerTest.java new file mode 100644 index 0000000000..8c284bbd0f --- /dev/null +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/TraversalStrategySerializerTest.java @@ -0,0 +1,91 @@ +/* + * 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.util.ser.binary.types; + +import io.netty.buffer.ByteBufAllocator; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; +import org.apache.tinkerpop.gremlin.structure.io.Buffer; +import org.apache.tinkerpop.gremlin.structure.io.binary.DataType; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; +import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry; +import org.apache.tinkerpop.gremlin.structure.io.binary.types.TraversalStrategySerializer; +import org.apache.tinkerpop.gremlin.util.TestTraversalStrategies.DummyTraversalStrategy; +import org.apache.tinkerpop.gremlin.util.ser.NettyBufferFactory; +import org.junit.Test; + +import java.util.Collections; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TraversalStrategySerializerTest { + private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private final NettyBufferFactory bufferFactory = new NettyBufferFactory(); + + @Test + public void shouldRejectUnregisteredTraversalStrategy() throws Exception { + final GraphBinaryWriter writer = new GraphBinaryWriter(); + final GraphBinaryReader reader = new GraphBinaryReader(); + final Buffer buffer = bufferFactory.create(allocator.buffer()); + buffer.writeByte(DataType.TRAVERSALSTRATEGY.getCodeByte()); + buffer.writeByte(0); + writer.writeValue("java.lang.Runtime", buffer, false); + writer.writeValue(Collections.emptyMap(), buffer, false); + buffer.readerIndex(0); + + try { + reader.read(buffer); + fail("Should have rejected an unregistered traversal strategy"); + } catch (Exception ex) { + assertThat(ex, instanceOf(IllegalArgumentException.class)); + assertEquals("TraversalStrategy class is not allowed: java.lang.Runtime", ex.getMessage()); + } + } + + @Test + public void shouldReadWriteRegisteredTraversalStrategy() throws Exception { + final TypeSerializerRegistry registry = TypeSerializerRegistry.build(). + addAllowedTraversalStrategy(DummyTraversalStrategy.class).create(); + assertReadWriteRegisteredTraversalStrategy(registry); + } + + @Test + public void shouldMergeAllowedTraversalStrategyWithRegisteredSerializer() throws Exception { + final TypeSerializerRegistry registry = TypeSerializerRegistry.build(). + add(TraversalStrategy.class, new TraversalStrategySerializer()). + addAllowedTraversalStrategy(DummyTraversalStrategy.class).create(); + assertReadWriteRegisteredTraversalStrategy(registry); + } + + private void assertReadWriteRegisteredTraversalStrategy(final TypeSerializerRegistry registry) throws Exception { + final GraphBinaryWriter writer = new GraphBinaryWriter(registry); + final GraphBinaryReader reader = new GraphBinaryReader(registry); + final Buffer buffer = bufferFactory.create(allocator.buffer()); + + writer.write(DummyTraversalStrategy.instance(), buffer); + buffer.readerIndex(0); + final TraversalStrategyProxy strategy = reader.read(buffer); + + assertEquals(DummyTraversalStrategy.class, strategy.getStrategyClass()); + } +}
