You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2016/12/01 15:33:33 UTC

[05/48] tinkerpop git commit: TINKERPOP-1562 Initial effort to deprecate ScriptEngines.

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/aca81c01
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/aca81c01
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/aca81c01

Branch: refs/heads/TINKERPOP-1562
Commit: aca81c011c1d6e2df9be8a0b4085db6987a1839a
Parents: ffd6543
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Thu Nov 17 16:47:31 2016 -0500
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Thu Dec 1 06:41:41 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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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/aca81c01/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();