TINKERPOP-1562 Initial effort to deprecate ScriptEngines.

This opens up some options for how to move plugins up to gremlin-core (or 
deprecate them completely - not surehow that will flow yet). Anyway, this is a 
wip commit and a nice stop point.


Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/ddafaef9
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/ddafaef9
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/ddafaef9

Branch: refs/heads/TINKERPOP-1562
Commit: ddafaef99a7a4dbf7d434b845f43e24e14deea19
Parents: 53e932b
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Thu Nov 17 16:47:31 2016 -0500
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Fri Dec 2 06:28:50 2016 -0500

----------------------------------------------------------------------
 .../gremlin/jsr223/AbstractGremlinModule.java   |  48 +++++
 .../gremlin/jsr223/CoreGremlinModule.java       |  43 ++++-
 .../gremlin/jsr223/ImportCustomizer.java        |   4 +
 .../gremlin/jsr223/ImportGremlinModule.java     | 174 +++++++++++++++++++
 .../gremlin/jsr223/ScriptCustomizer.java        |  56 ++++++
 .../gremlin/jsr223/ScriptFileModule.java        |  71 ++++++++
 .../gremlin/jsr223/ImportGremlinModuleTest.java | 149 ++++++++++++++++
 .../gremlin/groovy/engine/GremlinExecutor.java  |  76 +++++++-
 .../gremlin/groovy/engine/ScriptEngines.java    |   2 +
 .../tinkerpop/gremlin/server/Settings.java      |   8 +
 .../server/util/ServerGremlinExecutor.java      |  28 ++-
 11 files changed, 648 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/AbstractGremlinModule.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/AbstractGremlinModule.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/AbstractGremlinModule.java
new file mode 100644
index 0000000..36104f6
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/AbstractGremlinModule.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public abstract class AbstractGremlinModule implements GremlinModule {
+    protected final String moduleName;
+    protected final Customizer[] customizers;
+    protected final Set<String> appliesTo;
+
+    public AbstractGremlinModule(final String moduleName, final Set<String> 
appliesTo, final Customizer... customizers) {
+        this.moduleName = moduleName;
+        this.appliesTo = appliesTo;
+        this.customizers = customizers;
+    }
+
+    @Override
+    public String getName() {
+        return moduleName;
+    }
+
+    @Override
+    public Optional<Customizer[]> getCustomizers(final String 
scriptEngineName) {
+        return null == scriptEngineName || appliesTo.isEmpty() || 
appliesTo.contains(scriptEngineName) ?
+                Optional.of(customizers) : Optional.empty();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreGremlinModule.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreGremlinModule.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreGremlinModule.java
index dfe9f93..f1fdbe4 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreGremlinModule.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreGremlinModule.java
@@ -18,6 +18,8 @@
  */
 package org.apache.tinkerpop.gremlin.jsr223;
 
+import org.apache.tinkerpop.gremlin.util.CoreImports;
+
 import java.util.Optional;
 
 /**
@@ -25,21 +27,54 @@ import java.util.Optional;
  *
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class CoreGremlinModule implements GremlinModule {
+public final class CoreGremlinModule implements GremlinModule {
+
+    private static final String MODULE_NAME = "tinkerpop.core";
+
+    private static final ImportCustomizer gremlinCore = 
ImportCustomizer.build()
+            .addClassImports(CoreImports.getClassImports())
+            .addEnumImports(CoreImports.getEnumImports())
+            .addMethodImports(CoreImports.getMethodImports()).create();
 
-    private static final Optional<Customizer[]> CUSTOMIZERS = Optional.of(new 
Customizer[] { ImportCustomizer.GREMLIN_CORE });
+    private static final Customizer[] customizers = new Customizer[] 
{gremlinCore};
+    private static final Builder builder = new Builder();
 
+    /**
+     * @deprecated As of 3.2.4, replaced by {@link #instance()} as this field 
will later become private.
+     */
+    @Deprecated
     public static final CoreGremlinModule INSTANCE = new CoreGremlinModule();
 
     private CoreGremlinModule() {}
 
+    public static CoreGremlinModule instance() {
+        return INSTANCE;
+    }
+
     @Override
     public Optional<Customizer[]> getCustomizers(final String 
scriptEngineName) {
-        return CUSTOMIZERS;
+        return Optional.of(customizers);
     }
 
     @Override
     public String getName() {
-        return "tinkerpop.core";
+        return MODULE_NAME;
+    }
+
+    /**
+     * {@link GremlinModule} instances all use a builder pattern for 
instantiation via configuration. This method is
+     * just provided for consistency with that pattern. When instantiating 
programmatically, use {@link #instance()}.
+     */
+    public static Builder build() {
+        return builder;
+    }
+
+    public final static class Builder {
+
+        private Builder() {}
+
+        public CoreGremlinModule create() {
+            return instance();
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.java
index 6070839..2f0e08c 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.java
@@ -34,6 +34,10 @@ import java.util.Set;
  */
 public class ImportCustomizer implements Customizer {
 
+    /**
+     * @deprecated As of release 3.2.4, not replaced.
+     */
+    @Deprecated
     public static final ImportCustomizer GREMLIN_CORE = 
ImportCustomizer.build()
             .addClassImports(CoreImports.getClassImports())
             .addEnumImports(CoreImports.getEnumImports())

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModule.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModule.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModule.java
new file mode 100644
index 0000000..9fcbbce
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModule.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A module that allows custom class, static method and enum imports (i.e. 
those that are statically defined by a
+ * module within itself). A user might utilize this class to supply their own 
imports. This module is not specific
+ * to any {@link GremlinScriptEngine} - the imports are supplied to all 
engines.
+ *
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public final class ImportGremlinModule extends AbstractGremlinModule {
+    private static final String MODULE_NAME = "tinkerpop.import";
+
+    private ImportGremlinModule(final Builder builder) {
+        super(MODULE_NAME, builder.appliesTo, ImportCustomizer.build()
+                                                
.addClassImports(builder.classImports)
+                                                
.addEnumImports(builder.enumImports)
+                                                
.addMethodImports(builder.methodImports).create());
+    }
+
+    public static Builder build() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+        private static final Pattern METHOD_PATTERN = 
Pattern.compile("(.*)#(.*)\\((.*)\\)");
+        private static final Pattern ENUM_PATTERN = 
Pattern.compile("(.*)#(.*)");
+        private Set<Class> classImports = new HashSet<>();
+        private Set<Method> methodImports = new HashSet<>();
+        private Set<Enum> enumImports = new HashSet<>();
+        private final Set<String> appliesTo = new HashSet<>();
+
+        private Builder() {}
+
+        /**
+         * The name of the {@link GremlinScriptEngine} that this module will 
apply to. Setting no values here will
+         * make the module available to all the engines.
+         */
+        public Builder appliesTo(final Collection<String> scriptEngineName) {
+            this.appliesTo.addAll(scriptEngineName);
+            return this;
+        }
+
+        public Builder classImports(final Collection<String> classes) {
+            for (String clazz : classes) {
+                try {
+                    classImports.add(Class.forName(clazz));
+                } catch (Exception ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+            return this;
+        }
+
+        public Builder methodImports(final Collection<String> methods) {
+            for (String method : methods) {
+                try {
+                    if (method.endsWith("#*")) {
+                        final String classString = method.substring(0, 
method.length() - 2);
+                        final Class<?> clazz = Class.forName(classString);
+                        methodImports.addAll(allStaticMethods(clazz));
+                    } else {
+                        final Matcher matcher = METHOD_PATTERN.matcher(method);
+
+                        if (!matcher.matches())
+                            throw new 
IllegalArgumentException(String.format("Could not read method descriptor - 
check format of: %s", method));
+
+                        final String classString = matcher.group(1);
+                        final String methodString = matcher.group(2);
+                        final String argString = matcher.group(3);
+                        final Class<?> clazz = Class.forName(classString);
+                        methodImports.add(clazz.getMethod(methodString, 
parse(argString)));
+                    }
+                } catch (IllegalArgumentException iae) {
+                    throw iae;
+                } catch (Exception ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+            return this;
+        }
+
+        public Builder enumImports(final Collection<String> enums) {
+            for (String enumItem : enums) {
+                try {
+                    if (enumItem.endsWith("#*")) {
+                        final String classString = enumItem.substring(0, 
enumItem.length() - 2);
+                        final Class<?> clazz = Class.forName(classString);
+                        enumImports.addAll(allEnums(clazz));
+                    } else {
+                        final Matcher matcher = ENUM_PATTERN.matcher(enumItem);
+
+                        if (!matcher.matches())
+                            throw new 
IllegalArgumentException(String.format("Could not read enum descriptor - check 
format of: %s", enumItem));
+
+                        final String classString = matcher.group(1);
+                        final String enumValString = matcher.group(2);
+                        final Class<?> clazz = Class.forName(classString);
+
+                        Stream.of(clazz.getEnumConstants())
+                                .filter(e -> ((Enum) 
e).name().equals(enumValString))
+                                .findFirst().ifPresent(e -> 
enumImports.add((Enum) e));
+                    }
+                } catch (IllegalArgumentException iae) {
+                    throw iae;
+                } catch (Exception ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+            return this;
+        }
+
+        public ImportGremlinModule create() {
+            if (enumImports.isEmpty() && classImports.isEmpty() && 
methodImports.isEmpty())
+                throw new IllegalStateException("At least one import must be 
specified");
+
+            return new ImportGremlinModule(this);
+        }
+
+        private static List<Enum> allEnums(final Class<?> clazz) {
+            return Stream.of(clazz.getEnumConstants()).map(e -> (Enum) 
e).collect(Collectors.toList());
+        }
+
+        private static List<Method> allStaticMethods(final Class<?> clazz) {
+            return Stream.of(clazz.getMethods()).filter(m -> 
Modifier.isStatic(m.getModifiers())).collect(Collectors.toList());
+        }
+
+        private static Class<?>[] parse(final String argString) {
+            if (null == argString || argString.isEmpty())
+                return new Class<?>[0];
+
+            final List<String> args = 
Stream.of(argString.split(",")).map(String::trim).collect(Collectors.toList());
+            final Class<?>[] classes = new Class<?>[args.size()];
+            for (int ix = 0; ix < args.size(); ix++) {
+                try {
+                    classes[ix] = Class.forName(args.get(ix));
+                } catch (Exception ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+
+            return classes;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptCustomizer.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptCustomizer.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptCustomizer.java
new file mode 100644
index 0000000..28603df
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptCustomizer.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public class ScriptCustomizer implements Customizer {
+
+    private final Collection<List<String>> scripts;
+
+    public ScriptCustomizer(final Set<File> files) {
+        this(files.stream().map(f -> {
+            try {
+                return Files.lines(f.toPath(), 
StandardCharsets.UTF_8).collect(Collectors.toList());
+            } catch (IOException ioe) {
+                throw new IllegalStateException(ioe);
+            }
+        }).collect(Collectors.toList()));
+    }
+
+    public ScriptCustomizer(final Collection<List<String>> scripts) {
+        this.scripts = scripts;
+    }
+
+    public Collection<List<String>> scripts() {
+        return scripts;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptFileModule.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptFileModule.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptFileModule.java
new file mode 100644
index 0000000..b5c0d0c
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/ScriptFileModule.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public final class ScriptFileModule extends AbstractGremlinModule {
+    private static final String MODULE_NAME = "tinkerpop.script";
+
+    public ScriptFileModule(final Builder builder) {
+        super(MODULE_NAME, builder.appliesTo, new 
ScriptCustomizer(builder.files));
+    }
+
+    public static Builder build() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+
+        private final Set<String> appliesTo = new HashSet<>();
+        private Set<File> files = new HashSet<>();
+
+        private Builder() {}
+
+        /**
+         * The name of the {@link GremlinScriptEngine} that this module will 
apply to. Setting no values here will
+         * make the module available to all the engines.
+         */
+        public Builder appliesTo(final Collection<String> scriptEngineName) {
+            this.appliesTo.addAll(scriptEngineName);
+            return this;
+        }
+
+        public Builder files(final Set<String> files) {
+            for (String f : files) {
+                final File file = new File(f);
+                if (!file.exists()) throw new IllegalArgumentException(new 
FileNotFoundException(f));
+                this.files.add(file);
+            }
+
+            return this;
+        }
+
+        public ScriptFileModule create() {
+            return new ScriptFileModule(this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModuleTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModuleTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModuleTest.java
new file mode 100644
index 0000000..ce62357
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinModuleTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.jsr223;
+
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.io.IoCore;
+import org.apache.tinkerpop.gremlin.util.Gremlin;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public class ImportGremlinModuleTest {
+
+    @Test(expected = IllegalStateException.class)
+    public void shouldImportSomething() {
+        ImportGremlinModule.build().create();
+    }
+
+    @Test
+    public void shouldImportClass() {
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.classImports(Collections.singletonList(Graph.class.getCanonicalName())).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getClassImports(), hasItems(Graph.class));
+        assertEquals(1, customizer.getClassImports().size());
+    }
+
+    @Test
+    public void shouldImportWildcardMethod() throws Exception {
+        final Method zeroArgs = Gremlin.class.getMethod("version");
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.methodImports(Collections.singletonList(Gremlin.class.getCanonicalName() + 
"#*")).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getMethodImports(), hasItems(zeroArgs));
+
+        // will also have the static main() method
+        assertEquals(2, customizer.getMethodImports().size());
+    }
+
+    @Test
+    public void shouldImportZeroArgMethod() throws Exception {
+        final Method zeroArgs = Gremlin.class.getMethod("version");
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.methodImports(Collections.singletonList(toMethodDescriptor(zeroArgs))).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getMethodImports(), hasItems(zeroArgs));
+        assertEquals(1, customizer.getMethodImports().size());
+    }
+
+    @Test
+    public void shouldImportSingleArgMethod() throws Exception {
+        final Method singleArg = IoCore.class.getMethod("createIoBuilder", 
String.class);
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.methodImports(Collections.singletonList(toMethodDescriptor(singleArg))).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getMethodImports(), hasItems(singleArg));
+        assertEquals(1, customizer.getMethodImports().size());
+    }
+
+    @Test
+    public void shouldThrowExceptionIfInvalidMethodDescriptor() throws 
Exception {
+        final String badDescriptor = "Gremlin*version";
+        try {
+            ImportGremlinModule.build()
+                    
.methodImports(Collections.singletonList(badDescriptor)).create();
+            fail("Should have failed parsing the method descriptor");
+        } catch (IllegalArgumentException iae) {
+            assertEquals(iae.getMessage(), "Could not read method descriptor - 
check format of: " + badDescriptor);
+        }
+    }
+
+    @Test
+    public void shouldImportWildcardEnum() throws Exception {
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.enumImports(Collections.singletonList(T.class.getCanonicalName() + 
"#*")).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getEnumImports(), hasItems(T.id, T.key, T.label, 
T.value));
+        assertEquals(4, customizer.getEnumImports().size());
+    }
+
+    @Test
+    public void shouldImportEnum() throws Exception {
+        final ImportGremlinModule module = ImportGremlinModule.build()
+                
.enumImports(Collections.singletonList(T.class.getCanonicalName() + "#" + 
T.id.name())).create();
+
+        final ImportCustomizer customizer = (ImportCustomizer) 
module.getCustomizers().get()[0];
+        assertEquals(1, module.getCustomizers().get().length);
+        assertThat(customizer.getEnumImports(), hasItems(T.id));
+    }
+
+    @Test
+    public void shouldThrowExceptionIfInvalidEnumDescriptor() throws Exception 
{
+        final String badDescriptor = "T*id";
+        try {
+            ImportGremlinModule.build()
+                    
.enumImports(Collections.singletonList(badDescriptor)).create();
+            fail("Should have failed parsing the enum descriptor");
+        } catch (IllegalArgumentException iae) {
+            assertEquals("Could not read enum descriptor - check format of: " 
+ badDescriptor, iae.getMessage());
+        }
+    }
+
+    private static String toMethodDescriptor(final Method method) {
+        return method.getDeclaringClass().getCanonicalName() +
+                "#" +
+                method.getName() +
+                '(' +
+                String.join(",", Stream.of(method.getParameters()).map(p -> 
p.getType().getCanonicalName()).collect(Collectors.toList())) +
+                ')';
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
index cc30f99..90a3b43 100644
--- 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
+++ 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
@@ -22,6 +22,9 @@ import org.apache.commons.lang.exception.ExceptionUtils;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine;
 import org.apache.tinkerpop.gremlin.groovy.plugin.GremlinPlugin;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.tinkerpop.gremlin.jsr223.CachedGremlinScriptEngineManager;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinModule;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngineManager;
 import org.javatuples.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,6 +36,7 @@ import javax.script.SimpleBindings;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -51,11 +55,11 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Execute Gremlin scripts against a {@code ScriptEngine} instance.  It is 
designed to host any JSR-223 enabled
@@ -79,7 +83,10 @@ public class GremlinExecutor implements AutoCloseable {
      */
     private ScriptEngines scriptEngines;
 
+    private GremlinScriptEngineManager manager;
+
     private final Map<String, EngineSettings> settings;
+    private final Map<String, Map<String, Map<String,Object>>> modules;
     private final long scriptEvaluationTimeout;
     private final Bindings globalBindings;
     private final List<List<String>> use;
@@ -92,6 +99,7 @@ public class GremlinExecutor implements AutoCloseable {
     private final Set<String> enabledPlugins;
     private final boolean suppliedExecutor;
     private final boolean suppliedScheduledExecutor;
+    private boolean useGremlinScriptEngineManager;
 
     private GremlinExecutor(final Builder builder, final boolean 
suppliedExecutor,
                             final boolean suppliedScheduledExecutor) {
@@ -104,10 +112,22 @@ public class GremlinExecutor implements AutoCloseable {
         this.afterFailure = builder.afterFailure;
         this.use = builder.use;
         this.settings = builder.settings;
+        this.modules = builder.modules;
         this.scriptEvaluationTimeout = builder.scriptEvaluationTimeout;
         this.globalBindings = builder.globalBindings;
         this.enabledPlugins = builder.enabledPlugins;
-        this.scriptEngines = createScriptEngines();
+
+        this.manager = new CachedGremlinScriptEngineManager();
+        initializeGremlinScriptEngineManager();
+
+        // this is temporary so that we can have backward compatibilty to the 
old plugin system and ScriptEngines
+        // approach to configuring Gremlin Server and GremlinExecutor. This 
code/check should be removed with the
+        // deprecated code around this is removed.
+        if (!useGremlinScriptEngineManager)
+            this.scriptEngines = createScriptEngines();
+        else
+            this.scriptEngines = null;
+
         this.suppliedExecutor = suppliedExecutor;
         this.suppliedScheduledExecutor = suppliedScheduledExecutor;
     }
@@ -284,7 +304,9 @@ public class GremlinExecutor implements AutoCloseable {
 
                 logger.debug("Evaluating script - {} - in thread [{}]", 
script, Thread.currentThread().getName());
 
-                final Object o = scriptEngines.eval(script, bindings, lang);
+                // do this weirdo check until the now deprecated ScriptEngines 
is gutted
+                final Object o = useGremlinScriptEngineManager ?
+                        manager.getEngineByName(lang).eval(script, bindings) : 
scriptEngines.eval(script, bindings, lang);
 
                 // apply a transformation before sending back the result - 
useful when trying to force serialization
                 // in the same thread that the eval took place given 
ThreadLocal nature of graphs as well as some
@@ -396,6 +418,46 @@ public class GremlinExecutor implements AutoCloseable {
         return future;
     }
 
+    private void initializeGremlinScriptEngineManager() {
+        this.useGremlinScriptEngineManager = !modules.entrySet().isEmpty();
+
+        for (Map.Entry<String, Map<String, Map<String,Object>>> config : 
modules.entrySet()) {
+            final String language = config.getKey();
+            final Map<String, Map<String,Object>> moduleConfigs = 
config.getValue();
+            for (Map.Entry<String, Map<String,Object>> moduleConfig : 
moduleConfigs.entrySet()) {
+                try {
+                    final Class<?> clazz = 
Class.forName(moduleConfig.getKey());
+                    final Method builderMethod = clazz.getMethod("build");
+                    Object moduleBuilder = builderMethod.invoke(null);
+
+                    final Class<?> builderClazz = moduleBuilder.getClass();
+                    final Map<String,Object> customizerConfigs = 
moduleConfig.getValue();
+                    final Method[] methods = builderClazz.getMethods();
+                    for (Map.Entry<String,Object> customizerConfig : 
customizerConfigs.entrySet()) {
+                        final Method configMethod = 
Stream.of(methods).filter(m -> 
m.getName().equals(customizerConfig.getKey())).findFirst()
+                                .orElseThrow(() -> new 
IllegalStateException("Could not find builder method on " + 
builderClazz.getCanonicalName()));
+                        if (null == customizerConfig.getValue())
+                            moduleBuilder = configMethod.invoke(moduleBuilder);
+                        else
+                            moduleBuilder = configMethod.invoke(moduleBuilder, 
customizerConfig.getValue());
+                    }
+
+                    try {
+                        final Method appliesTo = 
builderClazz.getMethod("appliesTo");
+                        moduleBuilder = appliesTo.invoke(moduleBuilder, 
language);
+                    } catch (NoSuchMethodException ignored) {
+
+                    }
+
+                    final Method create = builderClazz.getMethod("create");
+                    manager.addModule((GremlinModule) 
create.invoke(moduleBuilder));
+                } catch (Exception ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+        }
+    }
+
     private ScriptEngines createScriptEngines() {
         // plugins already on the path - ones static to the classpath
         final List<GremlinPlugin> globalPlugins = new ArrayList<>();
@@ -490,6 +552,9 @@ public class GremlinExecutor implements AutoCloseable {
     public final static class Builder {
         private long scriptEvaluationTimeout = 8000;
         private Map<String, EngineSettings> settings = new HashMap<>();
+
+        private Map<String, Map<String, Map<String,Object>>> modules = new 
HashMap<>();
+
         private ExecutorService executorService = null;
         private ScheduledExecutorService scheduledExecutorService = null;
         private Set<String> enabledPlugins = new HashSet<>();
@@ -528,6 +593,11 @@ public class GremlinExecutor implements AutoCloseable {
             return this;
         }
 
+        public Builder addModules(final String engineName, final Map<String, 
Map<String,Object>> modules) {
+            this.modules.put(engineName, modules);
+            return this;
+        }
+
         /**
          * Bindings to apply to every script evaluated. Note that the entries 
of the supplied {@code Bindings} object
          * will be copied into a newly created {@link 
org.apache.tinkerpop.gremlin.jsr223.ConcurrentBindings} object

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/ScriptEngines.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/ScriptEngines.java
 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/ScriptEngines.java
index 3984dbb..6911419 100644
--- 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/ScriptEngines.java
+++ 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/ScriptEngines.java
@@ -64,7 +64,9 @@ import java.util.stream.Stream;
  * Holds a batch of the configured {@code ScriptEngine} objects for the server.
  *
  * @author Stephen Mallette (http://stephen.genoprime.com)
+ * @deprecated As of release 3.2.4, not directly replaced - see {@link 
GremlinScriptEngineManager}.
  */
+@Deprecated
 public class ScriptEngines implements AutoCloseable {
     private static final Logger logger = 
LoggerFactory.getLogger(ScriptEngines.class);
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
----------------------------------------------------------------------
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
index 4bb2089..a8395bb 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
@@ -20,6 +20,8 @@ package org.apache.tinkerpop.gremlin.server;
 
 import io.netty.handler.ssl.SslContext;
 import org.apache.tinkerpop.gremlin.driver.MessageSerializer;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinModule;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.server.auth.AllowAllAuthenticator;
@@ -258,6 +260,7 @@ public class Settings {
         scriptEngineSettingsDescription.putListPropertyType("staticImports", 
String.class);
         scriptEngineSettingsDescription.putListPropertyType("scripts", 
String.class);
         scriptEngineSettingsDescription.putMapPropertyType("config", 
String.class, Object.class);
+        scriptEngineSettingsDescription.putMapPropertyType("modules", 
String.class, Object.class);
         constructor.addTypeDescription(scriptEngineSettingsDescription);
 
         final TypeDescription sslSettings = new 
TypeDescription(SslSettings.class);
@@ -336,6 +339,11 @@ public class Settings {
          * {@code ScriptEngine} implementation being used.
          */
         public Map<String, Object> config = null;
+
+        /**
+         * A set of configurations for {@link GremlinModule} instances to 
apply to this {@link GremlinScriptEngine}.
+         */
+        public Map<String,Map<String,Object>> modules = new HashMap<>();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/ddafaef9/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
----------------------------------------------------------------------
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
index c8870ed..eda1d8d 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.server.util;
 
 import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor;
+import org.apache.tinkerpop.gremlin.jsr223.ImportGremlinModule;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.server.Channelizer;
 import org.apache.tinkerpop.gremlin.server.GraphManager;
@@ -28,7 +29,9 @@ import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -127,10 +130,27 @@ public class ServerGremlinExecutor<T extends 
ScheduledExecutorService> {
                 .scheduledExecutorService(this.scheduledExecutorService);
 
         settings.scriptEngines.forEach((k, v) -> {
-            // make sure that server related classes are available at init
-            v.imports.add(LifeCycleHook.class.getCanonicalName());
-            v.imports.add(LifeCycleHook.Context.class.getCanonicalName());
-            gremlinExecutorBuilder.addEngineSettings(k, v.imports, 
v.staticImports, v.scripts, v.config);
+            // use modules if they are present and the old approach if not
+            if (v.modules.isEmpty()) {
+                // make sure that server related classes are available at init 
- ultimately this body of code will be
+                // deleted when deprecation is removed
+                v.imports.add(LifeCycleHook.class.getCanonicalName());
+                v.imports.add(LifeCycleHook.Context.class.getCanonicalName());
+                gremlinExecutorBuilder.addEngineSettings(k, v.imports, 
v.staticImports, v.scripts, v.config);
+            } else {
+                // make sure that server related classes are available at init 
- ultimately this is the right way to
+                // do things going forward.
+                // TODO: though this Import is kinda sketchy.
+                if 
(v.modules.containsKey(ImportGremlinModule.class.getName())) {
+                    final List<String> listToAddImportsTo = (List<String>) 
v.modules.get(ImportGremlinModule.class.getName()).get("classImports");
+                    
listToAddImportsTo.addAll(Arrays.asList(LifeCycleHook.class.getName(), 
LifeCycleHook.Context.class.getName()));
+                } else {
+                    final Map<String,Object> imports = new HashMap<>();
+                    imports.put("classImports", 
Arrays.asList(LifeCycleHook.class.getName(), 
LifeCycleHook.Context.class.getName()));
+                    v.modules.put(ImportGremlinModule.class.getName(), 
imports);
+                }
+                gremlinExecutorBuilder.addModules(k, v.modules);
+            }
         });
 
         gremlinExecutor = gremlinExecutorBuilder.create();

Reply via email to