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/02 11:32:35 UTC

[15/50] tinkerpop git commit: TINKERPOP-1562 Move RemoteAcceptor related stuff to gremlin-core.

TINKERPOP-1562 Move RemoteAcceptor related stuff to gremlin-core.


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

Branch: refs/heads/TINKERPOP-1562
Commit: d7d6d5b2b8788fa420223d8986c27c535255494c
Parents: 8ee2225
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Mon Nov 21 13:13:30 2016 -0500
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Fri Dec 2 06:28:50 2016 -0500

----------------------------------------------------------------------
 .../console/jsr223/GephiRemoteAcceptor.groovy   | 372 +++++++++++++++++++
 .../console/plugin/GephiRemoteAcceptor.groovy   |   1 +
 .../tinkerpop/gremlin/jsr223/GremlinModule.java |   9 +
 .../gremlin/jsr223/ImportGremlinModule.java     |   1 -
 .../gremlin/jsr223/RemoteAcceptor.java          |  81 ++++
 .../gremlin/jsr223/RemoteException.java         |  40 ++
 .../gremlin/groovy/plugin/RemoteAcceptor.java   |   2 +
 .../gremlin/groovy/plugin/RemoteException.java  |   2 +
 8 files changed, 507 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/jsr223/GephiRemoteAcceptor.groovy
----------------------------------------------------------------------
diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/jsr223/GephiRemoteAcceptor.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/jsr223/GephiRemoteAcceptor.groovy
new file mode 100644
index 0000000..11e1e5c
--- /dev/null
+++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/jsr223/GephiRemoteAcceptor.groovy
@@ -0,0 +1,372 @@
+/*
+ * 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.console.jsr223
+
+import groovy.json.JsonOutput
+import groovy.json.JsonSlurper
+import groovy.transform.CompileStatic
+import org.apache.http.client.methods.CloseableHttpResponse
+import org.apache.http.client.methods.HttpUriRequest
+import org.apache.http.client.methods.RequestBuilder
+import org.apache.http.entity.StringEntity
+import org.apache.http.impl.client.CloseableHttpClient
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.util.EntityUtils
+import org.apache.tinkerpop.gremlin.console.plugin.GephiTraversalVisualizationStrategy
+import org.apache.tinkerpop.gremlin.jsr223.RemoteAcceptor
+import org.apache.tinkerpop.gremlin.jsr223.RemoteException
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource
+import org.apache.tinkerpop.gremlin.structure.Edge
+import org.apache.tinkerpop.gremlin.structure.Graph
+import org.apache.tinkerpop.gremlin.structure.Vertex
+import org.codehaus.groovy.tools.shell.Groovysh
+import org.codehaus.groovy.tools.shell.IO
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ * @author Randall Barnhart (randompi@gmail.com)
+ */
+class GephiRemoteAcceptor implements RemoteAcceptor {
+
+    private String host = "localhost"
+    private int port = 8080
+    private String workspace = "workspace1"
+
+    private final Groovysh shell
+    private final IO io
+
+    private final Random rand = new Random();
+    boolean traversalSubmittedForViz = false
+    long vizStepDelay
+    private float[] vizStartRGBColor
+    private float[] vizDefaultRGBColor
+    private char vizColorToFade
+    private float vizColorFadeRate
+    private float vizStartSize
+    private float vizSizeDecrementRate
+    private Map vertexAttributes = [:]
+
+    private CloseableHttpClient httpclient = HttpClients.createDefault();
+
+    public GephiRemoteAcceptor(final Groovysh shell, final IO io) {
+        this.shell = shell
+        this.io = io
+
+        // traversal visualization defaults
+        vizStepDelay = 1000;                 // 1 second pause between viz of steps
+        vizStartRGBColor = [0.0f, 1.0f, 0.5f]  // light aqua green
+        vizDefaultRGBColor = [0.6f, 0.6f, 0.6f]  // light grey
+        vizColorToFade = 'g'                 // will fade so blue is strongest
+        vizColorFadeRate = 0.7               // the multiplicative rate to fade visited vertices
+        vizStartSize = 10
+        vizSizeDecrementRate = 0.33f
+    }
+
+    @Override
+    connect(final List<String> args) throws RemoteException {
+        if (args.size() >= 1)
+            workspace = args[0]
+
+        if (args.size() >= 2)
+            host = args[1]
+
+        if (args.size() >= 3) {
+            try {
+                port = Integer.parseInt(args[2])
+            } catch (Exception ex) {
+                throw new RemoteException("Port must be an integer value")
+            }
+        }
+
+        def vizConfig = " with stepDelay:$vizStepDelay, startRGBColor:$vizStartRGBColor, " +
+                "colorToFade:$vizColorToFade, colorFadeRate:$vizColorFadeRate, startSize:$vizStartSize," +
+                "sizeDecrementRate:$vizSizeDecrementRate"
+
+        return "Connection to Gephi - http://$host:$port/$workspace" + vizConfig
+    }
+
+    @Override
+    Object configure(final List<String> args) throws RemoteException {
+        if (args.size() < 2)
+            throw new RemoteException("Invalid config arguments - check syntax")
+
+        if (args[0] == "host")
+            host = args[1]
+        else if (args[0] == "port") {
+            try {
+                port = Integer.parseInt(args[1])
+            } catch (Exception ignored) {
+                throw new RemoteException("Port must be an integer value")
+            }
+        } else if (args[0] == "workspace")
+            workspace = args[1]
+        else if (args[0] == "stepDelay")
+            parseVizStepDelay(args[1])
+        else if (args[0] == "startRGBColor")
+            parseVizStartRGBColor(args[1])
+        else if (args[0] == "colorToFade")
+            parseVizColorToFade(args[1])
+        else if (args[0] == "colorFadeRate")
+            parseVizColorFadeRate(args[1])
+        else if (args[0] == "sizeDecrementRate")
+            parseVizSizeDecrementRate(args[1])
+        else if (args[0] == "startSize")
+            parseVizStartSize(args[1])
+        else if (args[0] == "visualTraversal") {
+            def graphVar = shell.interp.context.getVariable(args[1])
+            if (!(graphVar instanceof Graph))
+                throw new RemoteException("Invalid argument to 'visualTraversal' - first parameter must be a Graph instance")
+
+            def gVar = args.size() == 3 ? args[2] : "vg"
+            def theG = GraphTraversalSource.build().with(new GephiTraversalVisualizationStrategy(this)).create(graphVar)
+            shell.interp.context.setVariable(gVar, theG)
+        } else
+            throw new RemoteException("Invalid config arguments - check syntax")
+
+        return "Connection to Gephi - http://$host:$port/$workspace" +
+                " with stepDelay:$vizStepDelay, startRGBColor:$vizStartRGBColor, " +
+                "colorToFade:$vizColorToFade, colorFadeRate:$vizColorFadeRate, startSize:$vizStartSize," +
+                "sizeDecrementRate:$vizSizeDecrementRate"
+    }
+
+    @Override
+    @CompileStatic
+    Object submit(final List<String> args) throws RemoteException {
+        final String line = String.join(" ", args)
+        if (line.trim() == "clear") {
+            clearGraph()
+            io.out.println("Gephi workspace cleared")
+            return
+        }
+
+        // need to clear the vertex attributes
+        vertexAttributes.clear()
+
+        // this tells the GraphTraversalVisualizationStrategy that if the line eval's to a traversal it should
+        // try to visualize it
+        traversalSubmittedForViz = true
+
+        // get the colors/sizes back to basics before trying visualize
+        resetColorsSizes()
+
+        final Object o = shell.execute(line)
+        if (o instanceof Graph) {
+            clearGraph()
+            def graph = (Graph) o
+            def g = graph.traversal()
+            g.V().sideEffect { addVertexToGephi(g, it.get()) }.iterate()
+        }
+
+        traversalSubmittedForViz = false
+    }
+
+    @Override
+    void close() throws IOException {
+        httpclient.close()
+    }
+
+    /**
+     * Visits the last set of vertices traversed and degrades their color and size.
+     */
+    def updateVisitedVertices(def List except = []) {
+        vertexAttributes.keySet().findAll{ vertexId -> !except.contains(vertexId) }.each { String vertexId ->
+            def attrs = vertexAttributes[vertexId]
+            float currentColor = attrs.color
+            currentColor *= vizColorFadeRate
+
+            int currentSize = attrs["size"]
+            currentSize = Math.max(vizStartSize, currentSize - (currentSize * vizSizeDecrementRate))
+
+            vertexAttributes.get(vertexId).color = currentColor
+            vertexAttributes.get(vertexId).size = currentSize
+
+            changeVertexAttributes(vertexId)
+        }
+    }
+
+    def changeVertexAttributes(def String vertexId) {
+        def props = [:]
+        props.put(vizColorToFade.toString(), vertexAttributes[vertexId].color)
+        props.put("size", vertexAttributes[vertexId].size)
+        updateGephiGraph([cn: [(vertexId): props]])
+    }
+
+    /**
+     * Visit a vertex traversed and initialize its color and size.
+     */
+    def visitVertexInGephi(def Vertex v) {
+        def props = [:]
+        props.put('r', vizStartRGBColor[0])
+        props.put('g', vizStartRGBColor[1])
+        props.put('b', vizStartRGBColor[2])
+        props.put('size', vizStartSize * 2.5)
+        props.put('visited', 1)
+
+        updateGephiGraph([cn: [(v.id().toString()): props]])
+
+        vertexAttributes[v.id().toString()] = [color: vizStartRGBColor[fadeColorIndex()], size: vizStartSize * 2.5, touch: 1]
+    }
+
+    def fadeColorIndex() {
+        if (vizColorToFade == 'r')
+            return 0
+        else if (vizColorToFade == 'g')
+            return 1
+        else if (vizColorToFade == 'b')
+            return 2
+    }
+
+    def addVertexToGephi(def GraphTraversalSource g, def Vertex v, def boolean ignoreEdges = false) {
+        // grab the first property value from the strategies of values
+        def props = g.V(v).valueMap().next().collectEntries { kv -> [(kv.key): kv.value[0]] }
+        props << [label: v.label()]
+        props.put('r', vizDefaultRGBColor[0])
+        props.put('g', vizDefaultRGBColor[1])
+        props.put('b', vizDefaultRGBColor[2])
+        props.put('x', rand.nextFloat())
+        props.put('y', rand.nextFloat())
+        props.put('size', 10)
+        props.put('visited', 0)
+
+        // only add if it does not exist in graph already
+        if (!getFromGephiGraph([operation: "getNode", id: v.id().toString()]).isPresent())
+            updateGephiGraph([an: [(v.id().toString()): props]])
+
+        if (!ignoreEdges) {
+            g.V(v).outE().sideEffect {
+                addEdgeToGephi(g, it.get())
+            }.iterate()
+        }
+    }
+
+    @CompileStatic
+    def addEdgeToGephi(def GraphTraversalSource g, def Edge e) {
+        def props = g.E(e).valueMap().next()
+        props.put('label', e.label())
+        props.put('source', e.outVertex().id().toString())
+        props.put('target', e.inVertex().id().toString())
+        props.put('directed', true)
+        props.put('visited', 0)
+
+        // make sure the in vertex is there but don't add its edges - that will happen later as we are looping
+        // all vertices in the graph
+        addVertexToGephi(g, e.inVertex(), true)
+
+        // both vertices are definitely there now, so add the edge
+        updateGephiGraph([ae: [(e.id().toString()): props]])
+    }
+
+    def clearGraph() {
+        updateGephiGraph([dn: [filter: "ALL"]])
+    }
+
+    def resetColorsSizes() {
+        updateGephiGraph([cn: [filter: [nodeAttribute: [attribute: "visited", value: 1]],
+                               attributes: [size: 1, r: vizDefaultRGBColor[0], g: vizDefaultRGBColor[1], b: vizDefaultRGBColor[2]]]])
+        updateGephiGraph([ce: [filter: [edgeAttribute: [attribute: "visited", value: 1]],
+                               attributes: [size: 1, r: vizDefaultRGBColor[0], g: vizDefaultRGBColor[1], b: vizDefaultRGBColor[2]]]])
+    }
+
+    def getFromGephiGraph(def Map queryArgs) {
+        def requestBuilder = RequestBuilder.get("http://$host:$port/$workspace")
+        queryArgs.each { requestBuilder = requestBuilder.addParameter(it.key, it.value) }
+
+        def httpResp = makeRequest(requestBuilder.build())
+        def resp = EntityUtils.toString(httpResp.entity)
+
+        // gephi streaming plugin does not set the content type or respect the Accept header - treat as text
+        if (resp.isEmpty())
+            return Optional.empty()
+        else
+            return Optional.of(new JsonSlurper().parseText(resp))
+    }
+
+    def updateGephiGraph(def Map postBody) {
+        def requestBuilder = RequestBuilder.post("http://$host:$port/$workspace")
+                                           .addParameter("format", "JSON")
+                                           .addParameter("operation", "updateGraph")
+                                           .setEntity(new StringEntity(JsonOutput.toJson(postBody)))
+        EntityUtils.consume(makeRequest(requestBuilder.build()).entity)
+    }
+
+    private CloseableHttpResponse makeRequest(HttpUriRequest request) {
+        def httpResp = httpclient.execute(request)
+        if (httpResp.getStatusLine().getStatusCode() == 200) {
+            return httpResp
+        } else {
+            def resp = EntityUtils.toString(httpResp.entity)
+            throw new RuntimeException("Unsuccessful request to Gephi - [${httpResp.getStatusLine().getStatusCode()}] ${httpResp.getStatusLine().getReasonPhrase()} - $resp")
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Gephi - [$workspace]"
+    }
+
+    private void parseVizStepDelay(String arg) {
+        try {
+            vizStepDelay = Long.parseLong(arg)
+        } catch (Exception ignored) {
+            throw new RemoteException("The stepDelay must be a long value")
+        }
+    }
+
+    private void parseVizStartRGBColor(String arg) {
+        try {
+            vizStartRGBColor = arg[1..-2].tokenize(',')*.toFloat()
+            assert (vizStartRGBColor.length == 3)
+        } catch (Exception ignored) {
+            throw new RemoteException("The vizStartRGBColor must be an array of 3 float values, e.g. [0.0,1.0,0.5]")
+        }
+    }
+
+    private void parseVizColorToFade(String arg) {
+        try {
+            vizColorToFade = arg.charAt(0).toLowerCase();
+            assert (vizColorToFade == 'r' || vizColorToFade == 'g' || vizColorToFade == 'b')
+        } catch (Exception ignored) {
+            throw new RemoteException("The vizColorToFade must be one character value among: r, g, b, R, G, B")
+        }
+    }
+
+    private void parseVizColorFadeRate(String arg) {
+        try {
+            vizColorFadeRate = Float.parseFloat(arg)
+        } catch (Exception ignored) {
+            throw new RemoteException("The colorFadeRate must be a float value")
+        }
+    }
+
+    private void parseVizSizeDecrementRate(String arg) {
+        try {
+            vizSizeDecrementRate = Float.parseFloat(arg)
+        } catch (Exception ignored) {
+            throw new RemoteException("The sizeDecrementRate must be a float value")
+        }
+    }
+
+    private void parseVizStartSize(String arg) {
+        try {
+            vizStartSize = Float.parseFloat(arg)
+        } catch (Exception ignored) {
+            throw new RemoteException("The startSize must be a float value")
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/plugin/GephiRemoteAcceptor.groovy
----------------------------------------------------------------------
diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/plugin/GephiRemoteAcceptor.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/plugin/GephiRemoteAcceptor.groovy
index 4198444..30527a4 100644
--- a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/plugin/GephiRemoteAcceptor.groovy
+++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/plugin/GephiRemoteAcceptor.groovy
@@ -40,6 +40,7 @@ import org.codehaus.groovy.tools.shell.IO
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
  * @author Randall Barnhart (randompi@gmail.com)
+ * @deprecated As of release 3.2.4, replaced by {@link org.apache.tinkerpop.gremlin.console.jsr223.GephiRemoteAcceptor}
  */
 class GephiRemoteAcceptor implements RemoteAcceptor {
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinModule.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinModule.java
index 20e9ff8..1077cf3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinModule.java
@@ -58,4 +58,13 @@ public interface GremlinModule {
      * @param scriptEngineName The name of the {@code ScriptEngine} or null to get all the available {@code Customizers}
      */
     public Optional<Customizer[]> getCustomizers(final String scriptEngineName);
+
+    /**
+     * Allows a plugin to utilize features of the {@code :remote} and {@code :submit} commands of the Gremlin Console.
+     * This method does not need to be implemented if the plugin is not meant for the Console for some reason or
+     * if it does not intend to take advantage of those commands.
+     */
+    public default Optional<RemoteAcceptor> remoteAcceptor() {
+        return Optional.empty();
+    }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/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
index 9fcbbce..e9ca477 100644
--- 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
@@ -23,7 +23,6 @@ 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;

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteAcceptor.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteAcceptor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteAcceptor.java
new file mode 100644
index 0000000..f917c09
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteAcceptor.java
@@ -0,0 +1,81 @@
+/*
+ * 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.Closeable;
+import java.util.List;
+
+/**
+ * The Gremlin Console supports the {@code :remote} and {@code :submit} commands which provide standardized ways
+ * for plugins to provide "remote connections" to resources and a way to "submit" a command to those resources.
+ * A "remote connection" does not necessarily have to be a remote server.  It simply refers to a resource that is
+ * external to the console.
+ * <p/>
+ * By implementing this interface and returning an instance of it through {@link GremlinModule#remoteAcceptor()} a
+ * plugin can hook into those commands and provide remoting features.
+ *
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public interface RemoteAcceptor extends Closeable {
+
+    public static final String RESULT = "result";
+
+    /**
+     * Gets called when {@code :remote} is used in conjunction with the "connect" option.  It is up to the
+     * implementation to decide how additional arguments on the line should be treated after "connect".
+     *
+     * @return an object to display as output to the user
+     * @throws RemoteException if there is a problem with connecting
+     */
+    public Object connect(final List<String> args) throws RemoteException;
+
+    /**
+     * Gets called when {@code :remote} is used in conjunction with the {@code config} option.  It is up to the
+     * implementation to decide how additional arguments on the line should be treated after {@code config}.
+     *
+     * @return an object to display as output to the user
+     * @throws RemoteException if there is a problem with configuration
+     */
+    public Object configure(final List<String> args) throws RemoteException;
+
+    /**
+     * Gets called when {@code :submit} is executed.  It is up to the implementation to decide how additional
+     * arguments on the line should be treated after {@code :submit}.
+     *
+     * @return an object to display as output to the user
+     * @throws RemoteException if there is a problem with submission
+     */
+    public Object submit(final List<String> args) throws RemoteException;
+
+    /**
+     * If the {@code RemoteAcceptor} is used in the Gremlin Console, then this method might be called to determine
+     * if it can be used in a fashion that supports the {@code :remote console} command.  By default, this value is
+     * set to {@code false}.
+     * <p/>
+     * A {@code RemoteAcceptor} should only return {@code true} for this method if it expects that all activities it
+     * supports are executed through the {@code :sumbit} command. If the users interaction with the remote requires
+     * working with both local and remote evaluation at the same time, it is likely best to keep this method return
+     * {@code false}. A good example of this type of plugin would be the Gephi Plugin which uses {@code :remote config}
+     * to configure a local {@code TraversalSource} to be used and expects calls to {@code :submit} for the same body
+     * of analysis.
+     */
+    public default boolean allowRemoteConsole() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteException.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteException.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteException.java
new file mode 100644
index 0000000..a75e6d6
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/RemoteException.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * A mapper {@code Exception} to be thrown when there are problems with processing a command given to a
+ * {@link RemoteAcceptor}.  The message provided to the exception will
+ * be displayed to the user in the Console.
+ *
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public class RemoteException extends Exception {
+    public RemoteException(final String message) {
+        super(message);
+    }
+
+    public RemoteException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public RemoteException(final Throwable cause) {
+        super(cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteAcceptor.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteAcceptor.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteAcceptor.java
index 0cf8bac..b3d03fe 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteAcceptor.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteAcceptor.java
@@ -33,7 +33,9 @@ import java.util.List;
  * plugin can hook into those commands and provide remoting features.
  *
  * @author Stephen Mallette (http://stephen.genoprime.com)
+ * @deprecated As of release 3.2.4, replaced by {@link org.apache.tinkerpop.gremlin.jsr223.RemoteAcceptor};
  */
+@Deprecated
 public interface RemoteAcceptor extends Closeable {
 
     public static final String RESULT = "result";

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d7d6d5b2/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteException.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteException.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteException.java
index 62efda5..f41d123 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteException.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/plugin/RemoteException.java
@@ -24,7 +24,9 @@ package org.apache.tinkerpop.gremlin.groovy.plugin;
  * be displayed to the user in the Console.
  *
  * @author Stephen Mallette (http://stephen.genoprime.com)
+ * @deprecated As of release 3.2.4, replaced by {@link org.apache.tinkerpop.gremlin.jsr223.RemoteException};
  */
+@Deprecated
 public class RemoteException extends Exception {
     public RemoteException(final String message) {
         super(message);