blob: 43f8bc96c2c2dba74da99c137dfc74fbf5f2d1db [file] [log] [blame]
/*
* 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.groovy.jsr223;
import groovy.lang.MissingPropertyException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.junit.Assert;
import org.junit.Test;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class GremlinGroovyScriptEngineOverGraphTest {
@Test
public void shouldDoSomeGremlin() throws Exception {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final ScriptEngine engine = new GremlinGroovyScriptEngine();
final List list = new ArrayList();
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("marko", convertToVertexId(graph, "marko"));
bindings.put("temp", list);
assertEquals(list.size(), 0);
engine.eval("g.V(marko).out().fill(temp)",bindings);
assertEquals(list.size(), 3);
}
@Test
public void shouldLoadImports() throws Exception {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final ScriptEngine engineWithImports = new GremlinGroovyScriptEngine();
engineWithImports.put("g", g);
assertEquals(Vertex.class.getName(), engineWithImports.eval("Vertex.class.getName()"));
assertEquals(2l, engineWithImports.eval("g.V().has('age',gt(30)).count().next()"));
assertEquals(Direction.IN, engineWithImports.eval("Direction.IN"));
assertEquals(Direction.OUT, engineWithImports.eval("Direction.OUT"));
assertEquals(Direction.BOTH, engineWithImports.eval("Direction.BOTH"));
}
@Test
public void shouldProperlyHandleBindings() throws Exception {
final Graph graph = TinkerFactory.createClassic();
final GraphTraversalSource g = graph.traversal();
final ScriptEngine engine = new GremlinGroovyScriptEngine();
engine.put("g", g);
engine.put("marko", convertToVertexId(graph, "marko"));
Assert.assertEquals(g.V(convertToVertexId(graph, "marko")).next(), engine.eval("g.V(marko).next()"));
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("s", "marko");
bindings.put("f", 0.5f);
bindings.put("i", 1);
bindings.put("b", true);
bindings.put("l", 100l);
bindings.put("d", 1.55555d);
assertEquals(engine.eval("g.E().has('weight',f).next()", bindings), g.E(convertToEdgeId(graph, "marko", "knows", "vadas")).next());
assertEquals(engine.eval("g.V().has('name',s).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
assertEquals(engine.eval("g.V().sideEffect{it.get().property('bbb',it.get().value('name')=='marko')}.iterate();g.V().has('bbb',b).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
assertEquals(engine.eval("g.V().sideEffect{it.get().property('iii',it.get().value('name')=='marko'?1:0)}.iterate();g.V().has('iii',i).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
assertEquals(engine.eval("g.V().sideEffect{it.get().property('lll',it.get().value('name')=='marko'?100l:0l)}.iterate();g.V().has('lll',l).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
assertEquals(engine.eval("g.V().sideEffect{it.get().property('ddd',it.get().value('name')=='marko'?1.55555d:0)}.iterate();g.V().has('ddd',d).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
}
@Test
public void shouldClearBindingsBetweenEvals() throws Exception {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final ScriptEngine engine = new GremlinGroovyScriptEngine();
engine.put("g", g);
engine.put("marko", convertToVertexId(graph, "marko"));
assertEquals(g.V(convertToVertexId(graph, "marko")).next(), engine.eval("g.V(marko).next()"));
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("s", "marko");
assertEquals(engine.eval("g.V().has('name',s).next()", bindings), g.V(convertToVertexId(graph, "marko")).next());
try {
engine.eval("g.V().has('name',s).next()");
fail("This should have failed because s is no longer bound");
} catch (Exception ex) {
final Throwable t = ExceptionUtils.getRootCause(ex);
assertEquals(MissingPropertyException.class, t.getClass());
assertTrue(t.getMessage().startsWith("No such property: s for class"));
}
}
@Test
public void shouldBeThreadSafe() throws Exception {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final ScriptEngine engine = new GremlinGroovyScriptEngine();
int runs = 500;
final CountDownLatch latch = new CountDownLatch(runs);
final List<String> names = Arrays.asList("marko", "peter", "josh", "vadas", "stephen", "pavel", "matthias");
final Random random = new Random();
for (int i = 0; i < runs; i++) {
new Thread("test-thread-safe-" + i) {
public void run() {
String name = names.get(random.nextInt(names.size() - 1));
try {
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("name", name);
final Object result = engine.eval("t = g.V().has('name',name); if(t.hasNext()) { t } else { null }", bindings);
if (name.equals("stephen") || name.equals("pavel") || name.equals("matthias"))
assertNull(result);
else
assertNotNull(result);
} catch (ScriptException e) {
assertFalse(true);
} finally {
if (graph.features().graph().supportsTransactions())
g.tx().rollback();
}
latch.countDown();
}
}.start();
}
latch.await();
}
@Test
public void shouldBeThreadSafeOnCompiledScript() throws Exception {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
final CompiledScript script = engine.compile("t = g.V().has('name',name); if(t.hasNext()) { t } else { null }");
int runs = 500;
final CountDownLatch latch = new CountDownLatch(runs);
final List<String> names = Arrays.asList("marko", "peter", "josh", "vadas", "stephen", "pavel", "matthias");
final Random random = new Random();
for (int i = 0; i < runs; i++) {
new Thread("test-thread-safety-on-compiled-script-" + i) {
public void run() {
String name = names.get(random.nextInt(names.size() - 1));
try {
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("name", name);
Object result = script.eval(bindings);
if (name.equals("stephen") || name.equals("pavel") || name.equals("matthias"))
assertNull(result);
else
assertNotNull(result);
} catch (ScriptException e) {
//System.out.println(e);
assertFalse(true);
} finally {
if (graph.features().graph().supportsTransactions())
g.tx().rollback();
}
latch.countDown();
}
}.start();
}
latch.await();
}
@Test
public void shouldEvalGlobalClosuresEvenAfterEvictionOfClass() throws ScriptException {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("marko", convertToVertexId(graph, "marko"));
bindings.put("vadas", convertToVertexId(graph, "vadas"));
// strong referenced global closure
engine.eval("def isVadas(v){v.value('name')=='vadas'}", bindings);
assertEquals(true, engine.eval("isVadas(g.V(vadas).next())", bindings));
// phantom referenced global closure
bindings.put(GremlinGroovyScriptEngine.KEY_REFERENCE_TYPE, GremlinGroovyScriptEngine.REFERENCE_TYPE_PHANTOM);
engine.eval("def isMarko(v){v.value('name')=='marko'}", bindings);
try {
engine.eval("isMarko(g.V(marko).next())", bindings);
fail("the isMarko function should not be present");
} catch (Exception ex) {
}
assertEquals(true, engine.eval("def isMarko(v){v.value('name')=='marko'}; isMarko(g.V(marko).next())", bindings));
try {
engine.eval("isMarko(g.V(marko"
+ ").next())", bindings);
fail("the isMarko function should not be present");
} catch (Exception ex) {
}
bindings.remove(GremlinGroovyScriptEngine.KEY_REFERENCE_TYPE);
// isVadas class was a hard reference so it should still be hanging about
assertEquals(true, engine.eval("isVadas(g.V(vadas).next())", bindings));
}
@Test
public void shouldAllowFunctionsUsedInClosure() throws ScriptException {
final Graph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("#jsr223.groovy.engine.keep.globals", "phantom");
bindings.put("vadas", convertToVertexId(graph, "vadas"));
// this works on its own when the function and the line that uses it is in one "script". this is the
// current workaround
assertEquals(g.V(convertToVertexId(graph, "vadas")).next(), engine.eval("def isVadas(v){v.value('name')=='vadas'};g.V().filter{isVadas(it.get())}.next()", bindings));
// let's reset this piece and make sure isVadas is not hanging around.
engine.reset();
// validate that isVadas throws an exception since it is not defined
try {
engine.eval("isVadas(g.V(vadas).next())", bindings);
// fail the test if the above doesn't throw an exception
fail();
} catch (Exception ex) {
// this is good...we want this. it means isVadas isn't hanging about
}
// now...define the function separately on its own in one script
bindings.remove("#jsr223.groovy.engine.keep.globals");
engine.eval("def isVadas(v){v.value('name')=='vadas'}", bindings);
// make sure the function works on its own...no problem
assertEquals(true, engine.eval("isVadas(g.V(vadas).next())", bindings));
// make sure the function works in a closure...this generates a StackOverflowError
assertEquals(g.V(convertToVertexId(graph, "vadas")).next(), engine.eval("g.V().filter{isVadas(it.get())}.next()", bindings));
}
@Test
@org.junit.Ignore
public void shouldAllowUseOfClasses() throws ScriptException {
final Graph graph = TinkerFactory.createClassic();
final GraphTraversalSource g = graph.traversal();
GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
final Bindings bindings = engine.createBindings();
bindings.put("g", g);
bindings.put("vadas", convertToVertexId(graph, "vadas"));
// works when it's all defined together
assertEquals(true, engine.eval("class c { static def isVadas(v){v.value('name')=='vadas'}};c.isVadas(g.V(vadas).next())", bindings));
// let's reset this piece and make sure isVadas is not hanging around.
engine.reset();
// validate that isVadas throws an exception since it is not defined
try {
engine.eval("c.isVadas(g.V(vadas).next())", bindings);
// fail the test if the above doesn't throw an exception
fail("Function should be gone");
} catch (Exception ex) {
// this is good...we want this. it means isVadas isn't hanging about
}
// now...define the class separately on its own in one script...
// HERE'S an AWKWARD BIT.........
// YOU HAVE TO END WITH: null;
// ....OR ELSE YOU GET:
// javax.script.ScriptException: javax.script.ScriptException:
// org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack: No signature of method: c.main()
// is applicable for argument types: ([Ljava.lang.String;) values: [[]]
// WOULD BE NICE IF WE DIDN'T HAVE TO DO THAT
engine.eval("class c { static def isVadas(v){v.name=='vadas'}};null;", bindings);
// make sure the class works on its own...this generates: groovy.lang.MissingPropertyException: No such property: c for class: Script2
assertEquals(true, engine.eval("c.isVadas(g.V(vadas).next())", bindings));
}
@Test
public void shouldProcessUTF8Query() throws Exception {
final Graph graph = TinkerGraph.open();
final GraphTraversalSource g = graph.traversal();
final Vertex nonUtf8 = graph.addVertex("name", "marko", "age", 29);
final Vertex utf8Name = graph.addVertex("name", "轉注", "age", 32);
final ScriptEngine engine = new GremlinGroovyScriptEngine();
engine.put("g", g);
Traversal eval = (Traversal) engine.eval("g.V().has('name', 'marko')");
assertEquals(nonUtf8, eval.next());
eval = (Traversal) engine.eval("g.V().has('name','轉注')");
assertEquals(utf8Name, eval.next());
}
private Object convertToVertexId(final Graph graph, final String vertexName) {
return convertToVertex(graph, vertexName).id();
}
private Vertex convertToVertex(final Graph graph, final String vertexName) {
// all test graphs have "name" as a unique id which makes it easy to hardcode this...works for now
return graph.traversal().V().has("name", vertexName).next();
}
private Object convertToEdgeId(final Graph graph, final String outVertexName, String edgeLabel, final String inVertexName) {
return graph.traversal().V().has("name", outVertexName).outE(edgeLabel).as("e").inV().has("name", inVertexName).<Edge>select("e").next().id();
}
}