Skip to content

Commit a455b32

Browse files
Cole-Greerkenhuuu
authored andcommitted
Custom Strategy Construction (apache#2806)
This commit adapts TraversalStrategyProxy in Java to work based off of strategy names instead of strategy classes. This change allows the proxy to be used in traversals in order to pass custom/provider defined strategies and configurations into the gremlin script. The advantage of this approach is that the user does not need any concrete strategy class for custom strategies, and the only actions needed from a provider to support a custom strategy is to either register it for use with gremlin-lang, or add it to an import customizer for gremlin-groovy. The TraversalStrategy base class in python is similarly adapted to pass custom strategies into gremlin scripts.
1 parent c62d2c5 commit a455b32

File tree

9 files changed

+288
-9
lines changed

9 files changed

+288
-9
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
7070
* Updated `OptionsStrategy` in `gremlin-python` to take options directly as keyword arguments.
7171
* Added static `instance()` method to `ElementIdStrategy` to an instance with the default configuration.
7272
* Updated `ElementIdStrategy.getConfiguration()` to help with serialization.
73+
* Updated `TraversalStrategyProxy` to utilize strategy names instead of strategy classes
74+
* Use `TraversalStrategyProxy` in Java, or `TraversalStrategy` in Python to pass custom strategies in traversals
7375
7476
== TinkerPop 3.7.0 (Gremfir Master of the Pan Flute)
7577

docs/src/upgrade/release-4.x.x.asciidoc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,34 @@ g.with_strategies(OptionsStrategy(options=myOptions))
196196
g.with_strategies(OptionsStrategy(**myOptions))
197197
----
198198
199+
==== Custom Traversal Strategy Construction
200+
201+
Traversal strategy construction has been updated such that it is no longer required to have concrete classes for each
202+
strategy being added to a graph traversal (use of concrete classes remains viable and is recommended for "native"
203+
TinkerPop strategies). To use strategies without a concrete class, `TraversalStrategyProxy` can be used in Java, and
204+
`TraversalStrategy` in Python.
205+
206+
All the following examples will produce the script `g.withStrategies(new MyStrategy(config1:'my value',config2:123))`:
207+
208+
[source,java]
209+
----
210+
Map<String, Object> configMap = new LinkedHashMap<>();
211+
configMap.put("config1", "my value");
212+
configMap.put("config2", 123);
213+
TraversalStrategy strategyProxy = new TraversalStrategyProxy("MyStrategy", new MapConfiguration(configMap));
214+
215+
GraphTraversal traversal = g.withStrategies(strategyProxy);
216+
----
217+
218+
[source,python]
219+
----
220+
g.with_strategies(TraversalStrategy(
221+
strategy_name='MyStrategy',
222+
config1='my value',
223+
config2=123
224+
))
225+
----
226+
199227
==== Changes to Serialization
200228
201229
The GLVs will only support GraphBinary V4 and GraphSON support has been removed. This means that the serializer option

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.commons.configuration2.Configuration;
2323
import org.apache.commons.text.StringEscapeUtils;
2424
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
25+
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
2526
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
2627
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
2728
import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversal;
@@ -372,13 +373,23 @@ private String buildStrategyArgs(final Object[] arguments) {
372373
break;
373374
}
374375

375-
final Configuration configuration = ((TraversalStrategy) arguments[i]).getConfiguration();
376+
Configuration configuration;
377+
String strategyName;
378+
379+
// special handling for TraversalStrategyProxy
380+
if (arguments[i] instanceof TraversalStrategyProxy) {
381+
configuration = ((TraversalStrategy) arguments[i]).getConfiguration();
382+
strategyName = ((TraversalStrategyProxy) arguments[i]).getStrategyName();
383+
} else {
384+
configuration = ((TraversalStrategy) arguments[i]).getConfiguration();
385+
strategyName = arguments[i].getClass().getSimpleName();
386+
}
376387

377388
if (configuration.isEmpty()) {
378-
sb.append(arguments[i].getClass().getSimpleName());
389+
sb.append(strategyName);
379390
} else {
380391
sb.append("new ")
381-
.append(arguments[i].getClass().getSimpleName())
392+
.append(strategyName)
382393
.append("(");
383394

384395
configuration.getKeys().forEachRemaining(key -> {

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/TraversalStrategyProxy.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@
2020
package org.apache.tinkerpop.gremlin.process.traversal.strategy;
2121

2222
import org.apache.commons.configuration2.Configuration;
23+
import org.apache.commons.configuration2.MapConfiguration;
24+
import org.apache.tinkerpop.gremlin.language.grammar.TraversalStrategyVisitor;
2325
import org.apache.tinkerpop.gremlin.process.traversal.GremlinLang;
2426
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
2527
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
28+
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
2629
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
2730
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
2831

2932
import java.io.Serializable;
33+
import java.util.Collections;
3034

3135
/**
3236
* This class is for use with {@link GremlinLang} and for serialization purposes. It is not meant for direct use with
@@ -37,23 +41,50 @@
3741
public final class TraversalStrategyProxy<T extends TraversalStrategy> implements Serializable, TraversalStrategy {
3842

3943
private final Configuration configuration;
40-
private final Class<T> strategyClass;
44+
private final String strategyName;
45+
46+
public TraversalStrategyProxy(final String strategyName) {
47+
this(strategyName, new MapConfiguration(Collections.EMPTY_MAP));
48+
}
49+
50+
public TraversalStrategyProxy(final String strategyName, final Configuration configuration) {
51+
this.configuration = configuration;
52+
this.strategyName = strategyName;
53+
}
4154

4255
public TraversalStrategyProxy(final T traversalStrategy) {
43-
this((Class<T>) traversalStrategy.getClass(), traversalStrategy.getConfiguration());
56+
this(traversalStrategy.getClass().getSimpleName(), traversalStrategy.getConfiguration());
4457
}
4558

59+
/**
60+
* @deprecated
61+
* This constructor has been deprecated since 4.0.0 as TraversalStrategyProxy is now based around strategy names,
62+
* instead of strategy classes. Use {@link TraversalStrategyProxy#TraversalStrategyProxy(String, Configuration)}
63+
* instead.
64+
*/
65+
@Deprecated
4666
public TraversalStrategyProxy(final Class<T> strategyClass, final Configuration configuration) {
47-
this.configuration = configuration;
48-
this.strategyClass = strategyClass;
67+
this(strategyClass.getSimpleName(), configuration);
4968
}
5069

5170
public Configuration getConfiguration() {
5271
return this.configuration;
5372
}
5473

74+
public String getStrategyName() {
75+
return this.strategyName;
76+
}
77+
78+
/**
79+
* @deprecated
80+
* As of 4.0.0, TraversalStrategyProxy is now based around strategy names, instead of strategy classes. For
81+
* compatibility, this method will attempt to lookup the strategy name in {@link TraversalStrategies.GlobalCache}.
82+
* Use of {@link TraversalStrategyProxy#getStrategyName()} is preferred. If a class object is needed, users should
83+
* utilize a mapping of strategy name to strategy class which is appropriate for their environment.
84+
*/
85+
@Deprecated
5586
public Class<T> getStrategyClass() {
56-
return this.strategyClass;
87+
return (Class<T>) TraversalStrategies.GlobalCache.getRegisteredStrategyClass(strategyName).get();
5788
}
5889

5990
@Override

gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngineTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,35 @@
1818
*/
1919
package org.apache.tinkerpop.gremlin.jsr223;
2020

21+
import org.apache.commons.configuration2.Configuration;
22+
import org.apache.commons.configuration2.MapConfiguration;
2123
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
24+
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
25+
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
26+
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
2227
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
28+
import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
29+
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
30+
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxyTest;
2331
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
2432
import org.junit.Test;
2533

2634
import javax.script.Bindings;
2735
import javax.script.ScriptException;
2836
import javax.script.SimpleBindings;
2937

38+
import java.util.HashMap;
39+
import java.util.HashSet;
40+
import java.util.LinkedHashMap;
41+
import java.util.Map;
42+
import java.util.Optional;
43+
import java.util.Set;
44+
3045
import static org.hamcrest.MatcherAssert.assertThat;
3146
import static org.hamcrest.core.IsInstanceOf.instanceOf;
3247
import static org.junit.Assert.assertEquals;
48+
import static org.junit.Assert.assertNotNull;
49+
import static org.junit.Assert.assertTrue;
3350

3451
public class GremlinLangScriptEngineTest {
3552

@@ -59,4 +76,53 @@ public void shouldEvalGremlinScriptWithParameters() throws ScriptException {
5976
assertThat(result, instanceOf(Traversal.Admin.class));
6077
assertEquals(g.V(100, 1000, 10000).asAdmin().getGremlinLang(), ((Traversal.Admin) result).getGremlinLang());
6178
}
79+
80+
public static class TestStrategy<S extends TraversalStrategy> extends AbstractTraversalStrategy<S> {
81+
private final Configuration configuration;
82+
83+
private TestStrategy(final Configuration configuration) {
84+
this.configuration = configuration;
85+
}
86+
@Override
87+
public void apply(Traversal.Admin traversal) {
88+
// Do nothing
89+
}
90+
91+
@Override
92+
public Configuration getConfiguration() {
93+
return configuration;
94+
}
95+
96+
public static TestStrategy create(Configuration configuration) {
97+
return new TestStrategy(configuration);
98+
}
99+
}
100+
101+
@Test
102+
public void shouldReconstructCustomRegisteredStrategy() throws ScriptException {
103+
TraversalStrategies.GlobalCache.registerStrategy(TestStrategy.class);
104+
105+
GraphTraversal traversal = (GraphTraversal) scriptEngine.eval("g.withStrategies(new TestStrategy(stringKey:\"stringValue\",intKey:1,booleanKey:true)).V()");
106+
107+
TestStrategy reconstructedStrategy = traversal.asAdmin().getStrategies().getStrategy(TestStrategy.class).get();
108+
109+
assertNotNull(reconstructedStrategy);
110+
111+
MapConfiguration expectedConfig = new MapConfiguration(new HashMap<String, Object>() {{
112+
put("stringKey", "stringValue");
113+
put("intKey", 1);
114+
put("booleanKey", true);
115+
}});
116+
117+
Set<String> expectedKeys = new HashSet<>();
118+
Set<String> actualKeys = new HashSet<>();
119+
expectedConfig.getKeys().forEachRemaining((key) -> expectedKeys.add(key));
120+
reconstructedStrategy.getConfiguration().getKeys().forEachRemaining((key) -> actualKeys.add(key));
121+
122+
assertEquals(expectedKeys, actualKeys);
123+
124+
expectedKeys.forEach((key) -> {
125+
assertEquals(expectedConfig.get(Object.class, key), reconstructedStrategy.getConfiguration().get(Object.class, key));
126+
});
127+
}
62128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.tinkerpop.gremlin.process.traversal.strategy;
20+
21+
import org.apache.commons.configuration2.MapConfiguration;
22+
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
23+
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
24+
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
25+
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
26+
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
27+
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
28+
import org.junit.Test;
29+
import java.util.LinkedHashMap;
30+
import java.util.Map;
31+
32+
import static org.junit.Assert.assertEquals;
33+
34+
public class TraversalStrategyProxyTest {
35+
36+
private static class TestStrategy<S extends TraversalStrategy> extends AbstractTraversalStrategy<S> {
37+
@Override
38+
public void apply(Traversal.Admin traversal) {
39+
// Do nothing
40+
}
41+
}
42+
43+
@Test
44+
public void shouldAddCustomStrategyToGremlinScript() {
45+
TraversalStrategyProxy strategyProxy = new TraversalStrategyProxy("TestStrategy");
46+
GraphTraversalSource g = EmptyGraph.instance().traversal();
47+
GraphTraversal traversal = g.withStrategies(strategyProxy).V();
48+
assertEquals("g.withStrategies(TestStrategy).V()", traversal.asAdmin().getGremlinLang().getGremlin());
49+
}
50+
51+
@Test
52+
public void shouldAddCustomStrategyWithConfigToGremlinScript() {
53+
Map<String, Object> configMap = new LinkedHashMap<>();
54+
configMap.put("stringKey", "stringValue");
55+
configMap.put("intKey", 1);
56+
configMap.put("booleanKey", true);
57+
TraversalStrategyProxy strategyProxy = new TraversalStrategyProxy("TestStrategy", new MapConfiguration(configMap));
58+
59+
GraphTraversalSource g = EmptyGraph.instance().traversal();
60+
GraphTraversal traversal = g.withStrategies(strategyProxy).V();
61+
62+
assertEquals("g.withStrategies(new TestStrategy(stringKey:\"stringValue\",intKey:1,booleanKey:true)).V()", traversal.asAdmin().getGremlinLang().getGremlin());
63+
}
64+
}

0 commit comments

Comments
 (0)